/* Uiml.Net: a .Net UIML renderer (http://research.edm.uhasselt.be/kris/research/uiml.net) Copyright (C) 2005 Kris Luyten (kris.luyten@uhasselt.be) Expertise Centre for Digital Media (http://www.edm.uhasselt.be) Hasselt University This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ namespace Uiml { using System; using System.Xml; using System.Text; using System.Reflection; using System.Collections; using System.Globalization; using Uiml.Executing.Binding; using Uiml.LayoutManagement; /// /// Part, p.34 of UIML 3.0 spec /// Represents one instance of a widget or nothing. /// Can be nested to obtain hierarchical relationship. /// /// <!ELEMENT part (style?, content?, behavior?, part*, repeat*)> /// <!ATTLIST part /// id NMTOKEN #IMPLIED /// class NMTOKEN #IMPLIED /// source CDATA #IMPLIED /// where (first|last|before|after) "last" /// where-part NMTOKEN #IMPLIED /// how (union|cascade|replace) "replace" /// export (hidden|optional|required) "optional"> /// public class Part : UimlAttributes, IUimlElement { #if COMPACT public class Connection { private object m_obj; private MethodInfo m_method; public Connection() {} public Connection(object o, MethodInfo mi) { m_obj = o; m_method = mi; } public MethodInfo Method { get { return m_method; } set { m_method = value; } } public object Object { get { return m_obj; } set { m_obj = value; } } } #endif private ArrayList m_children; private ArrayList m_properties; private ArrayList m_layout; private Hashtable m_componentsByDepth; private string m_class; private Part parent = null; /// /// The (final) concrete interface object representing this part /// private System.Object m_uiObject; /// /// This member is used to signal an event to the application logic. It is /// initialized in the Connect method. /// private UimlEventArgs m_eventArgs = null; #if COMPACT private ArrayList m_connections = null; #endif public Part() { m_children = new ArrayList(); m_properties = new ArrayList(); m_layout = new ArrayList(); m_componentsByDepth = new Hashtable(); } public Part(string identifier) : this() { Identifier = identifier; } public Part(XmlNode n) : this() { Process(n); } public void Process(XmlNode n) { if(n.Name != IAM) return; ReadAttributes(n); ProcessSubTree(n); if(SourceAvailable) { ITemplateResolver templateResolver = Template.GetResolver(How); Template t = TemplateRepository.Instance.Query(Source); templateResolver.Resolve(t,this); } } private void ProcessSubTree(XmlNode n) { XmlAttributeCollection attr = n.Attributes; if(attr.GetNamedItem(CLASS)!=null) Class = attr.GetNamedItem(CLASS).Value; if(n.HasChildNodes) { XmlNodeList xnl = n.ChildNodes; for(int i=0; i /// Sets and gets the class of this part. This class identifier still /// has to be mapped onto a specific class using a vocabulary /// /// /// /// public String Class { get { return m_class; } set { m_class = value; } } public System.Object UiObject { get { return m_uiObject; } set { m_uiObject = value; } } public IEnumerator Properties { get { return m_properties.GetEnumerator(); } } public Part SearchPart(string checkIdentifier) { if(checkIdentifier == Identifier) return this; else if(m_children.Count == 0) return null; else { IEnumerator enumParts = GetSubParts(); while(enumParts.MoveNext()) { Part p = ((Part)enumParts.Current).SearchPart(checkIdentifier); if(p!=null) return p; } } //not found... return null; } public Property GetProperty(string name) { IEnumerator enumProps = Properties; while(enumProps.MoveNext()) { Property prop = (Property)enumProps.Current; if(prop.Name == name) return prop; } // not found return null; } public void GetLayouts(ref ArrayList layouts) { if (HasLayout) layouts.AddRange(this.ULayout); // do recursively for children foreach (Part p in Children) { p.GetLayouts(ref layouts); } } public ArrayList ULayout { get { if (m_layout.Count == 0) return null; else return m_layout; } } public Hashtable ComponentsByDepth { get { return m_componentsByDepth; } } public bool HasLayout { get { return ULayout != null; } } #if COMPACT public bool Connected { get { return m_connections != null; } } #endif public ArrayList Children { get { return m_children; } } public delegate void UimlEventHandler(Part sender, UimlEventArgs e); public event UimlEventHandler Signal; /// ///Java Listeners style support is added here by a delegate. The Object o ///is interested in the events generated by this part and the events of its subtree /// public void Connect(object o, UimlDocument doc) { Peers.Vocabulary voc = doc.SearchPeers(doc.Vocabulary).GetVocabulary(); // get a list of all the methods that are available on object o. Type t = o.GetType(); MethodInfo[] mtds = t.GetMethods(); foreach(MethodInfo mi in mtds) { UimlEventHandlerAttribute eHandler = GetEventHandler(mi); if(eHandler != null) { Console.WriteLine("Found UimlEventHandler [{0}]", mi.Name); Console.WriteLine("\tConnecting to \"{0}\" of type <{1}> ", Identifier, Class); initEventArgs(eHandler); #if !COMPACT Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), this, "OnWidgetEvent"); #else Delegate handler = new EventHandler(OnWidgetEvent); #endif // connect the Signal event to o's event handler #if !COMPACT UimlEventHandler ObjectHandler = (UimlEventHandler)Delegate.CreateDelegate(typeof(UimlEventHandler), o, mi.Name); Signal += ObjectHandler; #else /* This is done with a ugly hack in the .NET Compact framework: * - we keep a list of MethodInfo objects * - when the event should be signalled, we invoke these methods */ if(!Connected) m_connections = new ArrayList(); m_connections.Add(new Connection(o, mi)); #endif try { // only handle events of the class eHandler.Event string concreteEventName = voc.MapsOnHandler(eHandler.Event); string eventId = voc.GetEventFor(Class, concreteEventName); //Sometimes eventId is a composed event: //it is an event of a property of widget w //so first load the property of w, and then link with //the event of this property //. Type theType = UiObject.GetType(); int j = eventId.IndexOf('.'); object targetObject = UiObject; while(j!=-1) { String parentType = eventId.Substring(0,j); eventId = eventId.Substring(j+1,eventId.Length-j-1); PropertyInfo pInfo = theType.GetProperty(parentType); theType = pInfo.PropertyType; targetObject = pInfo.GetValue(targetObject, null); j = eventId.IndexOf('.'); } EventInfo eInfo = theType.GetEvent(eventId); //load the event info as provided by the mappings of Widget w eInfo.AddEventHandler(targetObject, handler); } catch(MappingNotFoundException e) { Console.WriteLine("\tCould not accomplish connection for part {0} of class {1}: the specified event was not found", Identifier, Class); } catch(NullReferenceException nre) { Console.WriteLine(nre); Console.WriteLine("\tCould not accomplish connection: make sure to connect the objects _after_ calling Render!"); } } } //invoke the same method on the children of this part, with the same object IEnumerator enumerator = Children.GetEnumerator(); while(enumerator.MoveNext()) { try { Part child = ((Part)(enumerator.Current)); child.m_eventArgs = m_eventArgs; child.Connect(o, doc); } catch(MappingNotFoundException e) { //TODO //Console.WriteLine("Warning: could not connect to child \"{0}\"", ((Part)enumerator.Current).Identifier); } } //each part should connect separately (and allow the //redundancy). Otherwise could change the event notification //behavior and we don't want that to happen! } /// ///Disconnects object o from this part. /// public void Disconnect(Object o, UimlDocument doc) { Console.WriteLine("TODO Part.Disconnect not fully implemented"); //difficult choice here: disconnecting involves first recreating the //same handler and then -= them from the Signal IEnumerator enumerator; #if !COMPACT ; //TODO TODO TODO #else enumerator = m_connections.GetEnumerator(); while(enumerator.MoveNext()) { if (((Connection)enumerator.Current).Object == o) m_connections.Remove(enumerator.Current); } #endif //invoke the same method on the children of this part, with the same object enumerator = Children.GetEnumerator(); while(enumerator.MoveNext()) ((Part)(enumerator.Current)).Disconnect(o, doc); } protected UimlEventHandlerAttribute GetEventHandler(MethodInfo mi) { object[] attrs = mi.GetCustomAttributes(true); ArrayList l = new ArrayList(); foreach(Attribute a in attrs) { if(a is UimlEventHandlerAttribute) { // there can be only one event handler per method, so return it return (UimlEventHandlerAttribute)a; } } // none found return null; } protected void OnWidgetEvent(object sender, EventArgs e) { #if COMPACT /* we are always connected when this method is called * this test could thus be eliminated */ if(Connected) { IEnumerator ce = m_connections.GetEnumerator(); while(ce.MoveNext()) { Connection conn = (Connection)ce.Current; conn.Method.Invoke(conn.Object, new object[] {this, m_eventArgs}); } } #else if(Signal!=null) Signal(this, m_eventArgs); #endif } protected void initEventArgs(UimlEventHandlerAttribute a) { if(m_eventArgs != null) return; // already set by parent if(a.Params == null) return; m_eventArgs = new UimlEventArgs(); for(int i = 0; i < a.Params.Length; i++) { Part p = SearchPart(a.Params[i]); if(p != null) m_eventArgs.AddPart(p); else Console.WriteLine("Could not add Part [{0}] to the UimlEventArgs; part not found", a.Params[i]); } } public override string ToString() { return ToStringTree(string.Empty, " ", "-"); } protected string ToStringTree(string prefix, string orgPrefix, string orgLevel) { StringBuilder sb = new StringBuilder(); sb.Append(prefix + orgLevel + Identifier); sb.Append("\n"); foreach (object ch in Children) { // only for parts, not for inline style elements if (ch is Part) { Part p = (Part) ch; sb.Append(p.ToStringTree(prefix + orgPrefix, orgPrefix, orgLevel)); } } return sb.ToString(); } public const string IAM = "part"; public const string PART = "part"; public const string CLASS = "class"; public const string STYLE = "style"; public const string CONTENT = "content"; public const string BEHAVIOUR = "behaviour"; public const string LAYOUT = "layout"; } }