I am coding Mac App on Xcode with Applescript.
I made a function that doesn't stop forever, but I can't stop it.
It starts when I push a button that is located on the window.
Because this function doesn't stop, the button appears pushed forever.
And I have to force quit with 'Command+Option+Escape'. (Even if you do this activity, this app may not be stop.)
I want release the button before the function starts, and I want to stop this function safely with pushing another button.
This is my example. To stop this, push the "Stop" button on Xcode.
property parent : class "NSObject"
property mylabel : missing value
on applicationWillFinishLaunching_(aNotification)
-- Insert code here to initialize your application before any files are opened
end applicationWillFinishLaunching_
on myStartButtonHandler_(sender)
my myForeverFunction()
end myStartButtonHandler_
on myStopButtonHandler_(sender)
--How can I stop "myForeverFunction"?
end myStopButtonHandler_
on myForeverFunction()
set a to 0
repeat 100 times
set a to a+1
mylabel's setStringValue_(a)
delay 1
end repeat
end myForeverFunction
on applicationShouldTerminate_(sender)
-- Insert code here to do any housekeeping before your application quits
return current application's NSTerminateNow
end applicationShouldTerminate_
This is the project file --> https://dl.dropboxusercontent.com/u/97497395/test.zip
Sorry I am Japanese, I can't write English very well.
Basically the interface of your application is controlled and updated on your apps main thread. Therefore if you run some code which ties up the main thread then your interface will not have a chance to update itself until the code is complete. So to fix that you run the code in a background thread and thus your interface will be able to update itself.
I don't know if you can do this in AppleScriptObjC because I'm not too familiar with it. Here's how I do it in objective-c. I create a handler (someHandler) and then run this code. Note that since this handler isn't run in the main thread which has an automatically generated release pool, you will have to create and drain a release pool in your handler.
[NSThread detachNewThreadSelector:#selector(someHandler) toTarget:self withObject:nil];
EDIT: Here's the autorelease pool you asked about. In a reference-counted environment do it this way...
-(void)someHandler {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do your stuff here
[pool drain];
}
With Automatic Reference Counting (ARC) you would do it this way...
-(void)someHandler {
#autoreleasepool {
// do your stuff here
}
}
So I'm not sure which applies to AppleScriptObjC. A quick google search turned up this post.
Right now your code is looping and looping and important things like the interface are never getting updated. If you call a doEventFetch function to run all of the queued processes, that should fix your problem. Just call it once every loop:
on doEventFetch()
repeat
tell current application's NSApp to set theEvent to nextEventMatchingMask_untilDate_inMode_dequeue_(((current application's NSLeftMouseDownMask) as integer) + ((current application's NSKeyDownMask) as integer), missing value, current application's NSEventTrackingRunLoopMode, true)
if theEvent is missing value then
exit repeat
else
tell current application's NSApp to sendEvent_(theEvent)
end if
end repeat
end doEventFetch
on loopFunc()
repeat
#Repeat stuff here...
doEventFetch()
end repeat
end loopFunc
Related
I'm doing an applescript cocoa app and I'd like to run a function as a notification with the name "Finished Downloading" is displayed.
The function changes a value displayed in the main window of the cocoa app and the notification is created with a terminal command line.
I've tried to use observers but couldn't really understand how they work so, even if it's probably the right thing to use, I couldn't get it to work.
property NSNotificationCenter : class "NSNotificationCenter"
script AppDelegate
property parent : class "NSObject"
on myFunction()
log "hey"
end myFunction
on applicationWillFinishLaunching_(aNotification)
-- Insert code here to initialize your application before any files are opened
set ws to workspaceClass's sharedWorkspace()
set nc to ws's notificationCenter()
tell nc to addObserver_selector_name_object_(me, "myFunction()", "Finished Downloading", missing value) --tried this
end applicationWillFinishLaunching_
end script
As the notification named "Finished Downloading" is displayed in the top right corner of the monitor this script should run myFunction
Hope for help, thanks.
Alex
EDIT: thanks to the answer of Willeke I now verified that it's not possible to accomplish what I was trying to do. See Observe for new System Notifications OSX
I'm implementing a scripting language where the user might be causing an endless loop by accident. I want to give the user the opportunity to cancel such a runaway loop by holding down the command key while typing the period (".") key.
Currently, once for every line, I check for cancellation with this code:
NSEvent * evt = [[NSApplication sharedApplication] nextEventMatchingMask: NSKeyDownMask untilDate: [NSDate date] inMode: WILDScriptExecutionEventLoopMode dequeue: YES];
if( evt )
{
NSString * theKeys = [evt charactersIgnoringModifiers];
if( (evt.modifierFlags & NSCommandKeyMask) && theKeys.length > 0 && [theKeys characterAtIndex: 0] == '.' )
{
// +++ cancel script execution here.
}
}
The problem with this is that it eats any keyboard events that the user might be typing while the script is running, even though scripts should be able to check for keypresses. Also, it doesn't dequeue the corresponding NSKeyUp events. But if I tell it to dequeue key up events as well, it might dequeue the keyUp for a keypress that was being held before my script started and my application might never find out the key was released.
Also, I would like to not dequeue any events until I know it is actually a cancel event, but there is no separate dequeue call, and it feels unreliable to just assume the frontmost event on a second call will be the same one. And even if it is guaranteed to be the first, that would mean that the user typing an 'a' and then Cmd-. would mean I only ever see the 'a' and never the Cmd-. behind it if I don't dequeue events.
Is there a better option than going to the old Carbon stand-by GetKeys()? Fortunately, that seems to be available in 64 bit.
Also, I'm thinking about adding an NSStatusItem that adds a button to cancel the script to the menu bar or so. But how would I process events in a way that doesn't let the user e.g. select a menu while a script expects to be ruler of the main thread?
Any suggestions? Recommendations?
Using -addLocalMonitorForEventsMatchingMask: as Dave suggests is probably the easiest way to go about this, yes.
I just wanted to add that despite your unreliable feeling, the event queue is really a queue, and events don't change order. It is perfectly safe (and standard practice in event loops) to call -nextEventMatchingMask:inMode:dequeue:NO, examine the event, determine that it is one you want to deal with, and then call -nextEventMatchingMask:inMode:dequeue:YES in order to consume it. Just make sure that your mask and mode are identical between the two calls.
I would suggest using an event monitor. Since you're asking NSApp for events, it would seem that you're running the script in the current process, so you only have to monitor events in your own process (and not globally).
There are several ways to do this (subclassing NSApplication and overriding -sendEvent:, putting in an event tap, etc), but the easiest way to do this would be with a local event monitor:
id eventHandler = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDown handler:^(NSEvent *event) {
// check the runloop mode
// check for cmd-.
// abort the script if necessary
return event;
}];
When you're all done monitoring for events, don't forget to unregister your monitor:
[NSEvent removeMonitor:eventHandler];
So there's +[NSEvent modifierFlags] which is intended as a replacement for GetKeys(). It doesn't cover your use case of the period key though, sadly.
The core problem here with the event queue is you want to be able to search it, which isn't something the API exposes. The only workaround to that I can think of is to dequeue all events into an array, checking for a Command-. event, and then re-queue them all using postEvent:atStart:. Not pretty.
Perhaps as an optimisation you could use +[NSEvent modifierFlags] to only check the event queue when the command key is held down, but that sounds open to race conditions to me.
So final suggestion, override -postEvent:atStart: (on either NSApplication or NSWindow) and see if you can fish out the desired info there. I think at worst it could be interesting for debugging.
I'm writing a time logging application using QtRuby on OSX. It is important that it periodically reminds users (my team) to log their time, preferably by coming to the foreground (unminimizing, if necessary). This is very un-mac-like, so I would accept a bouncing task tray icon. I can't get either to work.
Anyway, I've tried the following
self.show()
self.showNormal()
self.raise()
self.activateWindow()
self.maximize()
self.setWindowState(Qt::WindowActive)
self.setWindowState(Qt::WindowMaximized)
# Must execute this with GUI thread
msgbox = Qt::MessageBox.new()
msgbox.setText('LOG YOUR TIME!')
msgbox.exec()
All these commands seem to be ignored once minimised or in the background. When trying to popup the messagebox, I worked around the "Cannot create children for a parent that is in a different thread." error by emitting a signal, but events don't seem to be processed until the user activates the window.
Does anyone know how to pop-up a minimised window with QTRuby or even QT & C++ on OSX?
TIA
Luke
I used Qt's threads rather than ruby threads and everything is lovely now. Maybe be something to do with the global interpreter lock.
I replaced
Thread.new { loop { every_minute_do_on_diff_thread; sleep 60 } }
connect(self, SIGNAL('every_minute_do_signal()'), self, SLOT('every_minute_do()'))
def every_minute_do_on_diff_thread
emit(every_minute_do_signal())
end
with
timer = Qt::Timer.new(self);
connect(timer, SIGNAL('timeout()'), self, SLOT('every_minute_do()'))
timer.start(60000)
I'm building an application with a main window and a sub-window that I'd like to present as a modal session. So I'm using this code within the window controller:
self.session = [[NSApplication sharedApplication] beginModalSessionForWindow: self.window];
[[NSApplication sharedApplication] runModalSession: self.session];
However, regardless of where I put this code - in windowDidLoad, in a post-windowDidLoad call from the main window, or even in the window controller's init function - what I get is a modeless sub-window. The sub-window appears over the main window, but the main window continues to respond to input events.
What am I doing wrong?
nvm - found my answer by reading some examples. (Once again, hours of research before posting on Stack failed to yield answers, and I stumble across insight right after posting.)
For posterity, here's the answer: beginModalSessionForWindow doesn't work like runModalForWindow.
With runModal, you can execute that code anywhere, and Cocoa will immediately halt all processing except for the modal window. (Unfortunately, that includes timers tied to non-UI events for background processing.) The code that executes runModal is blocked, and resumes only after the modal window is closed. The system enforces the modality of the window.
beginModalSessionForWindow works very differently. Wherever you execute it, the code starts up the modal window and then keeps executing. In the general use case (as demonstrated in Apple's examples), if you call beginModal before a loop and then condition the loop on polling the status of the session, the loop can perform whatever other processing you want; and during the loop, it's also blocking normal UI events - just like any IBAction would when executing a long-running loop.
The point is that beginModalSession actually doesn't enforce any modality of the window. Rather, your code enforces the modality of the window by executing a long-running loop. If you don't use a loop, but instead just let the "modal" session run and resume the ordinary event loop... then your other windows get all event processing, including UI events. The "modal" window becomes modeless.
I will argue that "beginModalSessionForWindow" should really be called beginModelessSessionForWindow, because that's what it does: it creates a modeless window and returns.
I'm looking for a good way to tell my Cocoa application to quit itself. Rest assured that this will not be used for production code. I'm just looking for an easy way to run one test and then close the application during debugging.
I have found that exit(0); will close the app, but it bypasses all of the normal application exit procedures, and I would like to keep all of them in place.
Essentially I want things to work as if a user pulled "Quit" from the menu, but I want it to happen automatically after I have finished with my test.
My code currently looks like this:
#if (SUPERFANCY_TESTING_MODE)
[self doSomething];
exit(0); // <-- I need something better to go here
#endif
You can pretty much rest assured that your app is going to get killed at least some of the time. Thus, defending against exits the like of exit(0); is required.
However, NSApplication implements the -terminate: method.
[NSApp terminate: nil]; ought to do what you want.
I would generally suggest posting it via -performSelector:afterDelay: with a delay of 0.0 to force it to happen at the top of the next pass through the event loop.
Example:
[NSApp performSelector:#selector(terminate:) withObject:nil afterDelay:0.0];