/*
This file is part of leafdigital leafChat.
leafChat is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
leafChat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with leafChat. If not, see <http://www.gnu.org/licenses/>.
Copyright 2011 Samuel Marshall.
*/
package com.leafdigital.scripting;
import java.lang.reflect.*;
import java.util.*;
import org.w3c.dom.Element;
import com.leafdigital.ui.api.*;
import com.leafdigital.ui.api.TreeBox.Item;
import util.xml.*;
import leafchat.core.api.*;
import leafchat.core.api.FilterInfo.Parameter;
/** Script item that handles an event. */
@UIHandler("itemsettings.event")
public class ItemEvent extends UserCodeItem implements TreeBox.SingleSelectionHandler
{
private MessageInfo event=null;
private FilterInfo filter=null;
private String[] filterValues=null;
private String priority;
private InfoItem rootItem;
private LinkedList<EditBox> filterParamEdits = new LinkedList<EditBox>();
/**
* Constructs from XML.
* @param parent Owning script
* @param e XML element
* @param index Index within script
* @throws XMLException
* @throws GeneralException
*/
public ItemEvent(Script parent,Element e,int index) throws XMLException,GeneralException
{
super(parent,e,index);
rootItem=new InfoItem(parent.getContext().getMessageInfo(Msg.class),null);
String eventClass=XML.getRequiredAttribute(e,"class");
try
{
event=getContext().getMessageInfo(
Class.forName(eventClass).asSubclass(Msg.class));
}
catch(ClassNotFoundException ex)
{
throw new BugException("Item event requests unknown event: "+eventClass);
}
if(e.hasAttribute("priority"))
{
priority=e.getAttribute("priority");
if(!priority.equals("BEFORENORMAL") && !priority.equals("LAST")
&& !priority.equals("NORMAL"))
{
throw new BugException("Event priority not supported: "+priority);
}
}
else
{
priority="NORMAL";
}
if(XML.hasChild(e,"filter"))
{
Element filterEl=XML.getChild(e,"filter");
String filterClass=filterEl.getAttribute("class");
FilterInfo[] appropriateFilters=event.getAppropriateFilters();
for(int i=0;i<appropriateFilters.length;i++)
{
if(appropriateFilters[i].getFilterClass().getName().equals(filterClass))
{
filter=appropriateFilters[i];
}
}
if(filter==null)
throw new BugException("Item event requests unknown or unsupported filter: "+filterClass);
FilterInfo.Parameter[] filterParams=filter.getScriptingParameters();
filterValues=XML.getChildTexts(filterEl,"param");
if(filterParams.length!=filterValues.length)
throw new BugException("Item event supplies incorrect number of filter parameters");
}
}
/**
* Constructs blank item.
* @param parent Owning script
* @param index Index in script
*/
public ItemEvent(Script parent,int index)
{
super(parent,index);
rootItem=new InfoItem(parent.getContext().getMessageInfo(Msg.class),null);
priority="AFTERNORMAL";
// debugMessages(rootItem.info,0);
}
@Override
String getSourceInit()
{
StringBuffer sb=new StringBuffer();
sb.append("\t\tcontext.requestMessages("+event.getMessageClass().getName()+".class,new Item"+getIndex()+"(),\n");
if(filter!=null)
{
sb.append("\t\t\tnew "+filter.getFilterClass().getName()+"(");
Parameter[] params=filter.getScriptingParameters();
for(int i=0;i<params.length;i++)
{
if(i!=0) sb.append(",");
if(params[i].getType()==int.class)
{
try
{
sb.append(Integer.parseInt(filterValues[i])+"");
}
catch(NumberFormatException nfe)
{
throw new BugException("Unexpected (non-integer) value for integer filter parameter");
}
}
else
sb.append(getQuotedString(filterValues[i]));
}
sb.append("),\n");
}
sb.append("\t\t\tMsg.PRIORITY_"+priority+");\n");
return sb.toString();
}
@Override
String getSourceMethods()
{
String messageClass=event.getMessageClass().getName();
StringBuffer sb=new StringBuffer();
sb.append(
"\tpublic class Item"+getIndex()+"\n"+
"\t{"+
"\t\tpublic void msg("+messageClass+" msg)\n"+
"\t\t{\n"+
"\t\t\t"+event.getContextInit()+"\n");
String[] variables=event.getVariables().getNames();
for(int i=0;i<variables.length;i++)
{
sb.append("\t\t\t");
sb.append(event.getVariables().getDefinition(variables[i]));
sb.append("\n");
}
sb.append(
convertUserCode()+"\n"+
"\t\t}\n"+
"\t}\n");
return sb.toString();
}
/**
* Display debug information.
* @param info MessageInfo
* @param indent Indent for recursive calls
*/
public static void debugMessages(MessageInfo info,int indent)
{
for(int i=0;i<indent;i++)
System.out.print(" ");
System.out.print(info.getName());
System.out.print(" <");
FilterInfo[] filters=info.getAppropriateFilters();
for(int i=0;i<filters.length;i++)
{
if(i>0) System.out.print(",");
String name=filters[i].getName();
System.out.print(name);
}
System.out.print("> [");
Method[] methods=info.getMessageClass().getMethods();
boolean first=true;
for(int i=0;i<methods.length;i++)
{
String name=methods[i].getName();
if(name.startsWith("get") && !name.equals("getClass") &&
!Modifier.isStatic(methods[i].getModifiers()) &&
Modifier.isPublic(methods[i].getModifiers()))
{
if(first)
first=false;
else
System.out.print(",");
name=name.substring(3,4).toLowerCase()+name.substring(4);
System.out.print(name+":");
String returnType=methods[i].getReturnType().getName();
if(returnType.equals("[B"))
returnType="byte[]";
else if(returnType.equals("[[B"))
returnType="byte[][]";
else if(returnType.indexOf('.')!=-1)
returnType=returnType.substring(returnType.lastIndexOf('.')+1);
System.out.print(returnType);
}
}
System.out.println("]");
MessageInfo[] children=info.getSubclasses();
for(int i=0;i<children.length;i++)
{
debugMessages(children[i],indent+1);
}
}
@Override
void save(Element e)
{
super.save(e);
e.setAttribute("class",event.getMessageClass().getName());
e.setAttribute("priority",priority);
if(filter!=null)
{
Element filterEl=XML.createChild(e,"filter");
filterEl.setAttribute("class",filter.getFilterClass().getName());
for(int i=0;i<filterValues.length;i++)
XML.setText(XML.createChild(filterEl,"param"),filterValues[i]);
}
}
@Override
protected java.awt.Color getNormalStripeRGB()
{
return new java.awt.Color(0,0,128);
}
/** UI: List of message types */
public TreeBox messagesUI;
/** UI: Filter options */
public Dropdown filterUI;
/** UI: Filter settings */
public VerticalPanel filterSettingsUI;
/** UI: Description text */
public Label descriptionUI;
/** UI: Variables list */
public Label variablesUI;
/** UI: Priority dropdown */
public Dropdown priorityUI;
@Override
protected Page getPage(Button ok)
{
Page p=super.getPage(ok);
messagesUI.setHandler(this);
if(event==null)
{
selected(null);
}
else
{
InfoItem item=rootItem.find(event);
messagesUI.select(item);
selected(item);
if(filter==null)
{
filterUI.setSelected(null);
changeFilter();
}
else
{
filterUI.setSelected(filter);
changeFilter();
for(int i=0;i<filterValues.length;i++)
{
filterParamEdits.get(i).setValue(filterValues[i]);
}
}
}
priorityUI.addValue("BEFORENORMAL","Handle before normal processing");
priorityUI.addValue("LAST","Handle after normal processing");
if(priority.equals("NORMAL"))
priorityUI.setSelected("BEFORENORMAL");
else
priorityUI.setSelected(priority);
return p;
}
@Override
protected String getSummaryLabel()
{
StringBuffer label=new StringBuffer();
label.append("<key>"+XML.esc(event.getName())+"</key>");
if(filter!=null)
{
label.append(" [<key>"+XML.esc(filter.getName())+"</key> filter]");
for(int i=0;i<filterValues.length;i++)
{
label.append(" "+XML.esc(filter.getScriptingParameters()[i].getName())+"=<key>"+XML.esc(filterValues[i])+"</key>");
}
}
return label.toString();
}
@Override
public String getVariablesLabel()
{
return getVariablesLabel(event);
}
/**
* Lists variables for event type.
* @param info MessageInfo
* @return String listing events with type and name (in XML output format)
*/
public static String getVariablesLabel(MessageInfo info)
{
StringBuffer sb=new StringBuffer();
String[] variables=info.getVariables().getNames();
for(int i=0;i<variables.length;i++)
{
if(i!=0) sb.append(", ");
sb.append(info.getVariables().getType(variables[i]).getName().replaceFirst("^.*\\.","")+" <key>"+
variables[i]+"</key>");
}
return sb.toString();
}
@Override
protected void saveSettings()
{
MessageInfo selectedMessage=selectedEvent.getMessageInfo();
String selectedPriority=(String)priorityUI.getSelected();
FilterInfo selectedFilter=((FilterInfo)filterUI.getSelected());
FilterInfo.Parameter[] filterParams=selectedFilter==null ? null : selectedFilter.getScriptingParameters();
String[] enteredValues=null;
if(filterParams!=null)
{
enteredValues=new String[filterParams.length];
int count=0;
for(EditBox eb : filterParamEdits)
{
enteredValues[count++]=eb.getValue();
}
assert(count==enteredValues.length);
}
if(event!=null && event.equals(selectedMessage) && priority.equals(selectedPriority) &&
((filter==null && selectedFilter==null) || (filter!=null && filter.equals(selectedFilter))))
{
// Event and filter the same, how about filter parameters?
if(filter==null || Arrays.equals(filterValues,enteredValues)) return;
}
// OK, something changed, save it
event=selectedMessage;
filter=selectedFilter;
filterValues=enteredValues;
priority=selectedPriority;
markChanged();
}
static class InfoItem implements TreeBox.Item
{
InfoItem[] children;
InfoItem parent;
MessageInfo info;
InfoItem(MessageInfo info,InfoItem parent)
{
this.parent=parent;
this.info=info;
MessageInfo[] childrenInfo=info.getSubclasses();
List<InfoItem> l = new LinkedList<InfoItem>();
for(int i=0;i<childrenInfo.length;i++)
{
if(childrenInfo[i].equals(info))
throw new BugException("Message loop on "+info.getName()+" (check that a MessageInfo isn't set up with the wrong class parameter)");
if(childrenInfo[i].allowScripting())
l.add(new InfoItem(childrenInfo[i],this));
}
children = l.toArray(new InfoItem[l.size()]);
}
MessageInfo getMessageInfo()
{
return info;
}
@Override
public TreeBox.Item[] getChildren()
{
return children;
}
@Override
public java.awt.Image getIcon()
{
return null;
}
@Override
public TreeBox.Item getParent()
{
return parent;
}
@Override
public String getText()
{
return info.getName();
}
@Override
public boolean isLeaf()
{
return children.length==0;
}
InfoItem find(MessageInfo searchInfo)
{
if(searchInfo.equals(info))
return this;
for(int i=0;i<children.length;i++)
{
InfoItem found=children[i].find(searchInfo);
if(found!=null) return found;
}
return null;
}
}
@Override
public TreeBox.Item getRoot()
{
return rootItem;
}
@Override
public boolean isRootDisplayed()
{
return false;
}
private InfoItem selectedEvent;
@Override
public void selected(Item i)
{
if(i==null)
{
filterUI.clear();
filterUI.setEnabled(false);
selectedEvent=(InfoItem)i;
descriptionUI.setVisible(false);
variablesUI.setText("");
change();
changeFilter();
}
else
{
filterUI.clear();
filterUI.addValue(null,"All messages of this type");
MessageInfo info=((InfoItem)i).getMessageInfo();
FilterInfo[] filters=info.getAppropriateFilters();
int validFilters=0;
for(int filter=0;filter<filters.length;filter++)
{
if(filters[filter].getScriptingParameters()!=null)
{
filterUI.addValue(filters[filter], filters[filter].getName());
validFilters++;
}
}
filterUI.setEnabled(validFilters>0);
if(info.getDescription()==null)
{
descriptionUI.setVisible(false);
}
else
{
descriptionUI.setText(info.getDescription());
descriptionUI.setVisible(true);
}
selectedEvent=(InfoItem)i;
variablesUI.setText(getVariablesLabel(info));
change();
changeFilter();
}
}
/** Callback: Filter changed */
@UIAction
public void changeFilter()
{
filterSettingsUI.removeAll();
filterParamEdits.clear();
if(filterUI.getSelected()!=null)
{
UI ui=getContext().getSingle(UI.class);
FilterInfo.Parameter[] parameters=((FilterInfo)filterUI.getSelected()).getScriptingParameters();
for(int i=0;i<parameters.length;i++)
{
BorderPanel bp=ui.newBorderPanel();
filterSettingsUI.add(bp);
bp.setSpacing(8);
Label l=ui.newLabel();
bp.set(BorderPanel.WEST,l);
l.setText(parameters[i].getName());
l.setWidthGroup("labels");
l.setBaseGroup("param"+i);
VerticalPanel vp=ui.newVerticalPanel();
bp.set(BorderPanel.CENTRAL,vp);
vp.setSpacing(2);
EditBox eb=ui.newEditBox();
vp.add(eb);
eb.setBaseGroup("param"+i);
if(parameters[i].getType()==int.class)
{
eb.setRequire("[0-9]+");
}
else
{
eb.setRequire(".+");
}
eb.setOnChange("change");
filterParamEdits.add(eb);
if(parameters[i].getDescription()!=null)
{
l=ui.newLabel();
l.setText(parameters[i].getDescription());
l.setSmall(true);
vp.add(l);
}
}
}
change();
}
/** Callback: Event changed */
@UIAction
public void change()
{
boolean ok=true;
if(selectedEvent==null)
{
ok=false;
}
else
{
for(EditBox eb : filterParamEdits)
{
if(eb.getFlag()!=EditBox.FLAG_NORMAL)
{
ok=false;
break;
}
}
}
allowOK(ok);
}
}