I thought I would post this under a new thread, but creating a modeless window is an old topic. KniazidisR posted a great solution about three years ago and then disappeared from the board. I grew dependent on it until it started screwing up my file specs due to a well known Applescript bug, so I encapsulated the AppKit/Foundation stuff in a subscript and yanked some of the pointers (“missing value”) into properties.
That works great for opening and closing the window (but don’t turn the pointers into globals or everything really blows up).
Then I got ambitious. I want to update the text contents periodically without thrashing memory by closing and opening a new window. I managed to do this in the first code stub, which minimally alters KniazidisR’s work. However, once I started elaborating on it in various ways, the behavior became highly irregular. Note where and how the additions trigger failures based on my comments. Perhaps it’s just my older version of Applescript, but I suspect some kind of memory reliability problem.
Below that, you can find my subscript work that functions well in a broader toolkit library. But once I tried to extend it with an update feature, that worked only very, very rarely.
Bonus points if anybody can help me toss RTF text into the window or easily add a scroll bar.
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"
property givingUpAfter : 2 -- seconds
property wController : missing value -- outlet equivalent in AsObjC
-- with or without these next three items, it works the same
property aView : missing value
property aWin : missing value
property newView : missing value
set notificationTitle to "" -- no title
set {aWidth, aHeight} to {1200, 600}
set notificationText to "Happy New Year!!!"
--set notificationText to current application's NSString's stringWithString:notificationText
set paramObj to {aWidth, aHeight, notificationTitle, notificationText}
my performSelectorOnMainThread:"displayNotification:" withObject:(paramObj) waitUntilDone:true
newView's setString:"Message 2-------" -- < This totally works!! (and sometimes it doesn't)
delay givingUpAfter
set shortlist1 to "▶︎Time 1" & return & " Time 2" & return & " Time3"
set shortlist2 to " Time 1" & return & "▶︎Time 2" & return & " Time3"
set shortlist3 to " Time 1" & return & " Time 2" & return & "▶︎Time3"
--newView's setString:shortlist1 -- doesn't work
-- uncomment this block and the once successful "Message 2" change fails
--UpdateDisplayNotification(shortlist1)
--delay givingUpAfter
--UpdateDisplayNotification(shortlist2)
--delay givingUpAfter
--UpdateDisplayNotification(shortlist3)
--delay givingUpAfter
-- uncomment this block and the once successful "Message 2" change fails
--newView's setString:shortlist1
--delay givingUpAfter
--newView's setString:shortlist2
--delay givingUpAfter
--newView's setString:shortlist3
--delay givingUpAfter
-- commenting out this next line and using ClosedisplayNotification(wController) instead
-- suddenly causes unreliable behavior in the previous newView's setString:"Message 2" line
my wController's |close|()
--ClosedisplayNotification(wController)
on ClosedisplayNotification(wController)
my wController's |close|()
end ClosedisplayNotification
on UpdateDisplayNotification(newText)
newView's setString:newText
end UpdateDisplayNotification
on displayNotification:paramObj
copy paramObj to {aWidth, aHeight, aTitle, notificationText}
set aColor to current application's NSColor's colorWithDeviceRed:1 green:1 blue:1 alpha:1 -- white
set aView to current application's NSTextView's alloc()'s initWithFrame:(current application's NSMakeRect(0, 0, aWidth, aHeight))
aView's setRichText:true
aView's useAllLigatures:true
aView's setTextColor:(current application's NSColor's blackColor()) --
aView's setBackgroundColor:aColor
aView's setEditable:false
aView's setFont:(current application's NSFont's fontWithName:"Courier" |size|:14)
set aWin to makeWinWithView(aView, aWidth, aHeight, aTitle, 1.0)
aView's setString:notificationText
set my wController to current application's NSWindowController's alloc()
my (wController's initWithWindow:aWin)
my (wController's showWindow:me)
delay givingUpAfter
copy aView to newView
end displayNotification:
on makeWinWithView(aView, aWinWidth, aWinHeight, aTitle, alphaV)
set aScreen to current application's NSScreen's mainScreen()
set aFrame to {{0, 0}, {aWinWidth, aWinHeight}}
set aBacking to current application's NSTitledWindowMask
set aDefer to current application's NSBackingStoreBuffered
set aWin to current application's NSWindow's alloc()
(aWin's initWithContentRect:aFrame styleMask:aBacking backing:aDefer defer:false screen:aScreen)
aWin's setTitle:aTitle
aWin's setDelegate:me
aWin's setDisplaysWhenScreenProfileChanges:true
aWin's setHasShadow:true
aWin's setIgnoresMouseEvents:false
aWin's setLevel:(current application's NSNormalWindowLevel)
aWin's setOpaque:false
aWin's setAlphaValue:alphaV --append
aWin's setReleasedWhenClosed:true
aWin's |center|()
aWin's setContentView:aView
return aWin
end makeWinWithView
------------------------------- Second version in a handy encapsulating subscript
---- opens and closes a modeless window but can't consistently update its contents (only rarely works)
use AppleScript version "2.4"
use scripting additions
property aController : missing value -- outlet equivalent in AsObjC
property theView : missing value
property theWin : missing value
property StackView : missing value
set shortlist1 to "▶︎Time 1" & return & " Time 2" & return & " Time3"
set shortlist2 to " Time 1" & return & "▶︎Time 2" & return & " Time3"
set shortlist3 to " Time 1" & return & " Time 2" & return & "▶︎Time3"
-- these work fine and illustrate what I'm trying to do in the next block
set {aController, theView, theWin} to DisplayNotificationWindow("Playing Clipboard Times", shortlist1, 650, 450, aController, theView, theWin)
delay 1
closeNotification(aController, theView, theWin)
set {aController, theView, theWin} to DisplayNotificationWindow("Playing Clipboard Times", shortlist2, 650, 450, aController, theView, theWin)
delay 1
closeNotification(aController, theView, theWin)
set {aController, theView, theWin} to DisplayNotificationWindow("Playing Clipboard Times", shortlist3, 650, 450, aController, theView, theWin)
delay 1
closeNotification(aController, theView, theWin)
try
set {aController, theView, theWin} to DisplayNotificationWindow("Playing Clipboard Times", shortlist1, 650, 450, aController, theView, theWin)
reportlog()
delay 1
UpdateNotificationWindow(shortlist2, aController, theView, theWin)
--DisplayNotificationWindow("", shortlist2, 650, 450, aController, theView, theWin)
delay 1
UpdateNotificationWindow(shortlist3, aController, theView, theWin)
delay 1
closeNotification(aController, theView, theWin)
on error errorMssg number errorNumber
log "***Bomb***" & errorMssg & " #" & errorNumber
closeNotification(aController, theView, theWin)
end try
-- handlers using Foundation and Appkit must be individually wrapped like this because of a bug that damages file references and script-loading in the main code
-- this handler combines two code stubs on modeless message windows and script wrappers that evade nasty ' Use framework "Foundation" ' bugs
-- Now I just need a scroll bar and some .rtf output and I'll be cooking with real gas, as they say...
on DisplayNotificationWindow(NTitle, nText, Nx, NY, CloseController, someView, someWin) -- was aView when it worked (but that only happened once)
script theScript
property parent : a reference to current application
property wController : missing value -- outlet equivalent in AsObjC
property aView : missing value
property aWin : missing value
property notificationTitle : "" -- only needed for displayNotification
property notificationText : "" -- only needed for displayNotification
property aWidth : 300 -- only needed for displayNotification
property aHeight : 60 -- only needed for displayNotification
use framework "Foundation"
use framework "AppKit"
on DisplayNotificationWindow(NTitle, nText, Nx, NY, CloseController, someView, someWin)
if (NTitle is "") and (nText is "") then
tell me to CloseController's |close|() -- finding this exact construction drove me batty
return
else if NTitle is "" then
-- log checks confirm the pointers someWin & someView are correct across calls
someWin's setContentView:someView
someView's setString:nText -- This combination worked once but then never again
return
else
set {notificationTitle, notificationText, aWidth, aHeight} to {NTitle, nText, Nx, NY}
my performSelectorOnMainThread:"displayNotification:" withObject:({aWidth, aHeight, notificationTitle, current application's NSString's stringWithString:notificationText}) waitUntilDone:true
end if
return {wController, aView, aWin}
end DisplayNotificationWindow
on displayNotification:paramObj
copy paramObj to {aWidth, aHeight, aTitle, notificationText}
set aColor to current application's NSColor's colorWithDeviceRed:1 green:1 blue:1 alpha:1 -- white
set aView to current application's NSTextView's alloc()'s initWithFrame:(current application's NSMakeRect(0, 0, aWidth, aHeight))
aView's setRichText:true
aView's useAllLigatures:true
aView's setTextColor:(current application's NSColor's blackColor()) -- could be cyanColor, whiteColor, etc...
aView's setBackgroundColor:aColor
aView's setEditable:false
aView's setFont:(current application's NSFont's fontWithName:"Courier" |size|:14)
set aScreen to current application's NSScreen's mainScreen()
set aFrame to {{0, 0}, {aWidth, aHeight}}
set aBacking to current application's NSTitledWindowMask
set aDefer to current application's NSBackingStoreBuffered
set aWin to current application's NSWindow's alloc()
(aWin's initWithContentRect:aFrame styleMask:aBacking backing:aDefer defer:false screen:aScreen)
aWin's setTitle:aTitle
aWin's setDelegate:me
aWin's setDisplaysWhenScreenProfileChanges:true
aWin's setHasShadow:true
aWin's setIgnoresMouseEvents:false
aWin's setLevel:(current application's NSNormalWindowLevel)
aWin's setOpaque:false
aWin's setAlphaValue:0.9 -- append to 1.0 for opaque
aWin's setReleasedWhenClosed:true
aWin's |center|()
aWin's setContentView:aView
aView's setString:notificationText -- < I need to alter this field without closing/opening a new window
set my wController to current application's NSWindowController's alloc()
my (wController's initWithWindow:aWin)
my (wController's showWindow:me)
delay 1
set astring to current application's NSString's stringWithString:(reverse of (characters of notificationText) as string) -- nada
--aWin's setContentView:aView
aView's setString:"Message 2" -- this fails here but it works in a more primitive setting
copy aView to StackView -- didn't help either
end displayNotification:
end script
return theScript's DisplayNotificationWindow(NTitle, nText, Nx, NY, CloseController, someView, someWin)
end DisplayNotificationWindow
on UpdateNotificationWindow(newText, theController, theView, theWin)
my DisplayNotificationWindow("", newText, 0, 0, theController, theView, theWin)
end UpdateNotificationWindow
on closeNotification(theController, theView, theWin)
my DisplayNotificationWindow("", "", 0, 0, theController, theView, theWin)
end closeNotification