Wonky Objective C pointer problems with a handy modeless window routine in Applescript

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

The first half of your script works for me in Tahoe, so you might need to add some details about what you are actually doing. Pasting RTF will work if you make the textView editable, although you can only paste where there is already text.