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()
7 Likes

very cool, so more of a desktop integration.