Toast Notifications

Has anyone worked on creating toast notifications? The idea would be to place a transparent container on top of a window and display templates with a message that would float up and disappear over time. They could be interactive too.

from com.inductiveautomation.factorypmi.application.components.template import TemplateHolder
from com.inductiveautomation.factorypmi.application.script import PyComponentWrapper
root = system.gui.getParentWindow(event).rootContainer.getComponent('contToasts')
newobj = TemplateHolder()
newobj.initTemplate(root.getAppContext())
newobj.setTemplatePath("toast")
newobj.setName("dynToast")
newobj.getLoadedTemplate().startup()
newobj.getLoadedTemplate().setEnableLayout(1)
root.addComponent(newobj)
PyComponentWrapper(newobj).Message = "Hello from Toast"
#system.gui.moveComponent(newobj, 100,100)
#system.gui.resizeComponent(newobj, 400,400)

system.gui.transform(newobj, newX=0, newY=500, duration=2000)

Can this be somehow ported/used in Ignition?
https://github.com/jithurjacob/Windows-10-Toast-Notifications

Looks like a bunch of Win32 API in it. I think I’m halfway there with the above code, just not knowing how to deal with the undocumented TemplateHolder I thought I would ask the experts so I don’t have to bang my head for several days.

Ok I’m halfway there. How do I destroy a template holder?

http://recordit.co/b8FWGAKQSX

from com.inductiveautomation.factorypmi.application.components.template import TemplateHolder
from com.inductiveautomation.factorypmi.application.script import PyComponentWrapper

root = system.gui.getParentWindow(event).rootContainer.getComponent('toasts')
epoch = system.date.toMillis(system.date.now())
holder = TemplateHolder()
holder.initTemplate(root.getAppContext())
holder.setTemplatePath(root.TemplatePath)
holder.setName(str(epoch))
holder.getLoadedTemplate().startup()
holder.getLoadedTemplate().setEnableLayout(1)

root.addComponent(holder)

instance = PyComponentWrapper(holder)
instance.Message = "time is "+ str(epoch)

#TODO: not sure why I have to resize it, but if I don't then the template instance is huge
system.gui.resizeComponent(holder, root.TemplateWidth, root.TemplateHeight)

toastCount = len(root.getComponents())
#TODO: holder.height, instance.height, instance.getHeight() return zero
yOffset = (toastCount * root.TemplateHeight) + (toastCount * root.Padding)
system.gui.moveComponent(holder, 0, root.height - yOffset)

'''
add toast anywhere
	system.util.sendMessage(message,severity)
	toast everywhere

invokeLater(root.DwellMs)
	if isPinned then pass
	transform off screen since fading is a pain in the ass
	remove from toasts
	trigger toasts.y < this.y to transform down
	
toast close
	transform off screen since fading is a pain in the ass
	remove from toasts
	trigger toasts.y < this.y to transform down
	
toast click
	explode toaster ?
	
toast overflow from screen
	do not invokeLater(root.DwellMs) until visible
	
toast unpin
	invokeLater(root.DwellMs)
'''

#system.gui.transform(newobj, newX=newobj.x+400, newY=newobj.y+100, duration=2000, coordSpace=system.gui.COORD_DESIGNER)

Ok, it pretty much works. I will post the code later.

http://recordit.co/XZGERsclNd

1 Like

Here is my way of doing this. I wrote this when 7.0 came out, so I would expect there are quite a few upgrades.

The benefit of this method is the toast notification will pop up outside of the Ignition window, so if the user is browsing the web, watching porn, etc, this will pop up on top of that.

Call the method with path.to.createNotifier()

notifiers = []
def createNotifier(title="Hello", bodyText="World", time = 5000, bgColor=None, textColor=None):
	from javax.swing import JFrame,JLabel,JTextArea,JButton
	from javax.swing.border import LineBorder
	from java.awt import Dimension,Color,Font
	from java.awt.geom import RoundRectangle2D
	from net.miginfocom.swing import MigLayout
	from com.inductiveautomation.ignition.client.images import PathIcon
	from java.lang import Runnable
	
	if not bgColor:
		bgColor = Color.BLACK
		
	if not textColor:
		textColor = Color.WHITE
		
	frame = JFrame(title)
	frame.setAlwaysOnTop(True)
	frame.setUndecorated(True)
	frame.setMinimumSize(Dimension(300, 50))
	frame.setMaximumSize(Dimension(300, 50))
	frame.setSize(Dimension(300, 50))
	frame.setFocusableWindowState(False)
	frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE)
	frame.setOpacity(0.75)

	pane = frame.getContentPane()
	pane.setLayout(MigLayout("insets 5"))
	pane.setBorder(LineBorder(Color(90, 90, 90)))
	pane.setOpaque(True)
	pane.setBackground(bgColor)

	header = JLabel(title)
	header.setForeground(textColor)
	header.setFont(Font("Dialog", Font.PLAIN, 20))

	body = JTextArea(bodyText)
	body.setForeground(textColor)
	body.setBackground(bgColor)
	body.setFont(Font("Dialog", Font.PLAIN, 16))
	body.setLineWrap(True)
	body.setWrapStyleWord(True)
	body.setEditable(False)
	body.setOpaque(True)

	pane.add(header);
	def closeFrame(evt=None,frame=frame):
		notifiers.remove(frame)
		frame.dispose()
		if len(notifiers):
			orderNotifiers()

	if time <= 0:
		icon = PathIcon(frame,"Builtin/icons/16/delete2.png",16,16,True,True)
		button = JButton(icon,actionPerformed=closeFrame)
		button.setContentAreaFilled(False)
		button.setBorder(None)
		pane.add(button,"dock east,gap 5 5 5 5,alignx center,aligny center")
	
	frame.pack()

	body.setSize(frame.getWidth(), 1)
	pane.add(body, "newline,span,push")

	frame.pack()

	shape = RoundRectangle2D.Float(0.0, 0.0,frame.getWidth(), frame.getHeight(), 15.0, 15.0)
	frame.setShape(shape)

	frame.setVisible(True)

	notifiers.append(frame)

	if time > 0:
		system.util.invokeLater(closeFrame, time)
		
	if len(notifiers):
		orderNotifiers()

def orderNotifiers():
	from java.awt import Toolkit
		
	gc = getApp().getGraphicsConfiguration()
		
	if gc:
		sb = gc.getBounds()
		x = sb.x + sb.width - 10
		y = sb.y + 10
	else:
		ss = Toolkit.getDefaultToolkit().getScreenSize()
		x = ss.width - 10
		y = 10
		
	for f in notifiers:
		f.setLocation(x - f.getWidth(), y)
		y += f.getHeight() + 10
		
def getApp():
	from com.inductiveautomation.factorypmi.application import FPMIApp
	return FPMIApp.getInstance()
8 Likes

very cool, so more of a desktop integration.

Hello, great work Sir, I have a question:

how to you call this from a function script ?

@cmisztur2 hasn't been seen in about 9 months, but looking at his video and his code, it appears that he is running his code directly from the action performed event handler of a button. The code posted by @Kyle_Chase is set up for a project library script, and the path.to portion of the call he posted can be whatever you set it up to be when you put the script in your library.
Example
image

If you put the above code in project library script that is unimaginatively called libraryScripts, then the call would be libraryScripts.createNotifier().

For use Kyle_Chase is necessary to install something in the server.

We are printing over TCP and using message handle on view to pop the messages, but we are having problem to catch the event on Ignition from the function.

But I will check it, thanks for replay :clap:t3:

These scripts were written for Vision, and for multiple reasons, they are not compatible with Perspective. If you're trying to create this functionality in a Perspective environment, you will have to go about it in a different way.