示例#1
0
        /// <summary>
        /// A linked list node that represents one step in a GUI path.
        /// A step in the typedPath is represented by this node.
        /// Following steps generate subsequent GuiPath nodes recursively.
        /// </summary>
        /// <param name="typedPath">a typed path</param>
        public GuiPath(string typedPath)
        {
            // remove the first step from the typedPath and process it
            int last = 0;
            string step = Utilities.GetFirstStep(typedPath, out last);
            Utilities.SplitTypedToken(step, out m_name, out m_type);
            if (m_type == "value" && (m_name == null || m_name == ""))
                m_name = ""; // want it to be empty
            else if (m_name == null || m_name == "") m_name = "NAMELESS";
            try
            {
                int roleValue = Convert.ToInt32(m_type);
                m_role = (AccessibleRole)roleValue;
            }
            catch (Exception)
            {
                m_role = Utilities.TypeToRole(m_type);
            }

            // Parse the name
            string name;
            string varId;
            m_Nth = parseIndex(m_name, out name, out varId);
            if (m_name == name) m_Nth = 1;
            m_name = name;
            m_varId = varId;

            // if not consumed, split off the end of the path as a child path
            if (1 + last < typedPath.Length)
            {
                m_child = new GuiPath(typedPath.Substring(last + 1));
                m_child.m_parent = this;
            }
        }
示例#2
0
        /// <summary>
        /// A linked list node that represents one step in a GUI path.
        /// A step in the typedPath is represented by this node.
        /// Following steps generate subsequent GuiPath nodes recursively.
        /// </summary>
        /// <param name="typedPath">a typed path</param>
        public GuiPath(string typedPath)
        {
            // remove the first step from the typedPath and process it
            int    last = 0;
            string step = Utilities.GetFirstStep(typedPath, out last);

            Utilities.SplitTypedToken(step, out m_name, out m_type);
            if (m_type == "value" && (m_name == null || m_name == ""))
            {
                m_name = "";                 // want it to be empty
            }
            else if (m_name == null || m_name == "")
            {
                m_name = "NAMELESS";
            }
            try
            {
                int roleValue = Convert.ToInt32(m_type);
                m_role = (AccessibleRole)roleValue;
            }
            catch (Exception)
            {
                m_role = Utilities.TypeToRole(m_type);
            }

            // Parse the name
            string name;
            string varId;

            m_Nth = parseIndex(m_name, out name, out varId);
            if (m_name == name)
            {
                m_Nth = 1;
            }
            m_name  = name;
            m_varId = varId;

            // if not consumed, split off the end of the path as a child path
            if (1 + last < typedPath.Length)
            {
                m_child          = new GuiPath(typedPath.Substring(last + 1));
                m_child.m_parent = this;
            }
        }
示例#3
0
        /// <summary>
        /// Find an Active Accessible object on an appPath via the
        /// gui model and/or a guiPath via Active Accessibility.
        /// The appPath is applied first if not null, then the guiPath.
        /// </summary>
        /// <param name="xmlPath">the xmlPath to a control or null</param>
        /// <param name="guiPath">the guiPath to click or null</param>
        /// <returns>The Active Accessible object or null</returns>
        private AccessibilityHelper findInGui(XmlPath xmlPath, string guiPath)
        {
            string path = null;

            if (xmlPath != null && xmlPath.isValid())
            {
                path = xmlPath.Path;
            }
            if (Utilities.isGoodStr(guiPath))
            {
                path += guiPath;
            }
            if (!Utilities.isGoodStr(path))
            {
                return(null);
            }
            GuiPath gp = new GuiPath(Utilities.evalExpr(path));

            if (gp == null || !gp.isValid())
            {
                return(null);
            }
            return(gp.FindInGui(m_ah, this));
        }
示例#4
0
 /// <summary>
 /// Find an Active Accessible object on an appPath via the
 /// gui model and/or a guiPath via Active Accessibility.
 /// The appPath is applied first if not null, then the guiPath.
 /// </summary>
 /// <param name="xmlPath">the xmlPath to a control or null</param>
 /// <param name="guiPath">the guiPath to click or null</param>
 /// <returns>The Active Accessible object or null</returns>
 private AccessibilityHelper findInGui(XmlPath xmlPath, string guiPath)
 {
     string path = null;
     if (xmlPath != null && xmlPath.isValid()) path = xmlPath.Path;
     if (Utilities.isGoodStr(guiPath)) path += guiPath;
     if (!Utilities.isGoodStr(path)) return null;
     GuiPath gp = new GuiPath(Utilities.evalExpr(path));
     if (gp == null || !gp.isValid()) return null;
     return gp.FindInGui(m_ah, this);
 }
示例#5
0
 /// <summary>
 /// Determines the result of a Simian sensation.
 /// Problems with sensors are not generally logged
 /// since they are frequently called and most are not
 /// fatal.
 /// </summary>
 /// <param name="sensorRef">A sensor expression in a rule.</param>
 /// <returns>true if the sensor detected its target.</returns>
 public bool sensation(EmptyElement sensorRef)
 {
     if (sensorRef.getName().Equals("window"))
     {   // is the window showing?
         string id = sensorRef.getValue("id"); // a VarNode
         string title = sensorRef.getValue("title");
         MarkedNode mn = null;
         if (Utilities.isGoodStr(id) && Utilities.isGoodStr(title))
         {  // fetch the title from the model node via id + title
             mn = m_varNodes.get(id);
             // bail out if id not defined yet.
             if (mn == null) return false;
             if (mn.node != null)
                 title = m_GuiModel.selectToString(mn.node, title, "title");
             if (!Utilities.isGoodStr(title)) return false;
         }
         else if (id == null && title == null)
         {   // get the main window title from the model
             title = m_Config.getDataBase()+m_GuiModel.getTitle();
         }
         if (title == null) return false;  // model lacks title
         if (Utilities.isGoodStr(title))
         {
             IntPtr winHand = FindWindow(null, title);
             if ((int)winHand != 0)
             {
                 AccessibilityHelper ah = new AccessibilityHelper(winHand);
                 // look for a titlebar
                 GuiPath gPath = new GuiPath("1:NAMELESS");
                 AccessibilityHelper tah = ah.SearchPath(gPath, this);
                 if (tah == null || !tah.Value.Equals(title)) return false;
                 m_ah = ah;
                 return true;
             }
         }
     }
     if (sensorRef.getName().Equals("tested"))
     {
         string id = sensorRef.getValue("id"); // which controls
         string control = sensorRef.getValue("control"); // which controls
         string count = sensorRef.getValue("count"); // indicates how many
         if (control != null && count == null) return false;
         if (control == null && count != null) return false;
         if (control == null && count == null && id == null) return false;
         if (id != null)
         {
             MarkedNode mn = m_varNodes.get(id);
             if (mn != null)
             {
                 if (mn.mark == null) return false;
                 return mn.mark.Equals("tested");
             }
         }
         // if id fails to return a marked node, try a control count
         if (control != null)
         {
             int n = 0; int k = 0;
             if (control.Equals("view"))
             {
                 if (m_views == null) m_views = new MarkedList(m_GuiModel, "//*[@role='view']");
                 n = m_views.Count();
                 k = m_views.Count("tested");
             }
             if (count.Equals("all") && n == k) return true;
             if (count.Equals("not-all") && k < n) return true;
             return false;
         }
         return false;
     }
     if (sensorRef.getName().Equals("glimpse"))
     {
         string id = sensorRef.getValue("id"); // a VarNode
         string appPath = sensorRef.getValue("on"); // an appPath
         string guiPath = sensorRef.getValue("at"); // a giuPath
         string property = sensorRef.getValue("prop"); // an ah property
         string expect = sensorRef.getValue("expect"); // value expected
         // Id provides a context ah that must be used to find the rest of the path!
         // can't just use the appPath from it. What if it's a dialog?
         XmlNode context = null;
         MarkedNode mn = null;
         if (Utilities.isGoodStr(id))
         {
             mn = m_varNodes.get(id);
             // bail out if id not defined yet.
             if (mn == null) return false;
             if (mn.node != null) context = mn.node;
         }
         return glimpse(context, appPath, guiPath, property, expect);
     }
     return false;
 }
示例#6
0
 /// <summary>
 /// Method to apply when a path node is not found.
 /// No path above it and none below will call this method;
 /// only the one not found.
 /// </summary>
 /// <param name="path">The path step last tried.</param>
 public void notFound(GuiPath path)
 {
     m_log.writeElt("not-found");
     m_log.writeAttr(path.Role.ToString(), path.Name);
     m_log.endElt();
 }
示例#7
0
 /// <summary>
 /// Method to apply when a path node is not found.
 /// No path above it and none below will call this method;
 /// only the one not found.
 /// </summary>
 /// <param name="path">The path step last tried.</param>
 public void notFound(GuiPath path)
 {
     m_log.writeElt("not-found");
     m_log.writeAttr(path.Role.ToString(), path.Name);
     m_log.endElt();
 }
示例#8
0
 /// <summary>
 /// Determines the result of a Simian sensation.
 /// Problems with sensors are not generally logged
 /// since they are frequently called and most are not
 /// fatal.
 /// </summary>
 /// <param name="sensorRef">A sensor expression in a rule.</param>
 /// <returns>true if the sensor detected its target.</returns>
 public bool sensation(EmptyElement sensorRef)
 {
     if (sensorRef.getName().Equals("window"))
     {                                                // is the window showing?
         string     id    = sensorRef.getValue("id"); // a VarNode
         string     title = sensorRef.getValue("title");
         MarkedNode mn    = null;
         if (Utilities.isGoodStr(id) && Utilities.isGoodStr(title))
         {                  // fetch the title from the model node via id + title
             mn = m_varNodes.get(id);
             // bail out if id not defined yet.
             if (mn == null)
             {
                 return(false);
             }
             if (mn.node != null)
             {
                 title = m_GuiModel.selectToString(mn.node, title, "title");
             }
             if (!Utilities.isGoodStr(title))
             {
                 return(false);
             }
         }
         else if (id == null && title == null)
         {                   // get the main window title from the model
             title = m_Config.getDataBase() + m_GuiModel.getTitle();
         }
         if (title == null)
         {
             return(false);                                // model lacks title
         }
         if (Utilities.isGoodStr(title))
         {
             IntPtr winHand = FindWindow(null, title);
             if ((int)winHand != 0)
             {
                 AccessibilityHelper ah = new AccessibilityHelper(winHand);
                 // look for a titlebar
                 GuiPath             gPath = new GuiPath("1:NAMELESS");
                 AccessibilityHelper tah   = ah.SearchPath(gPath, this);
                 if (tah == null || !tah.Value.Equals(title))
                 {
                     return(false);
                 }
                 m_ah = ah;
                 return(true);
             }
         }
     }
     if (sensorRef.getName().Equals("tested"))
     {
         string id      = sensorRef.getValue("id");            // which controls
         string control = sensorRef.getValue("control");       // which controls
         string count   = sensorRef.getValue("count");         // indicates how many
         if (control != null && count == null)
         {
             return(false);
         }
         if (control == null && count != null)
         {
             return(false);
         }
         if (control == null && count == null && id == null)
         {
             return(false);
         }
         if (id != null)
         {
             MarkedNode mn = m_varNodes.get(id);
             if (mn != null)
             {
                 if (mn.mark == null)
                 {
                     return(false);
                 }
                 return(mn.mark.Equals("tested"));
             }
         }
         // if id fails to return a marked node, try a control count
         if (control != null)
         {
             int n = 0; int k = 0;
             if (control.Equals("view"))
             {
                 if (m_views == null)
                 {
                     m_views = new MarkedList(m_GuiModel, "//*[@role='view']");
                 }
                 n = m_views.Count();
                 k = m_views.Count("tested");
             }
             if (count.Equals("all") && n == k)
             {
                 return(true);
             }
             if (count.Equals("not-all") && k < n)
             {
                 return(true);
             }
             return(false);
         }
         return(false);
     }
     if (sensorRef.getName().Equals("glimpse"))
     {
         string id       = sensorRef.getValue("id");           // a VarNode
         string appPath  = sensorRef.getValue("on");           // an appPath
         string guiPath  = sensorRef.getValue("at");           // a giuPath
         string property = sensorRef.getValue("prop");         // an ah property
         string expect   = sensorRef.getValue("expect");       // value expected
         // Id provides a context ah that must be used to find the rest of the path!
         // can't just use the appPath from it. What if it's a dialog?
         XmlNode    context = null;
         MarkedNode mn      = null;
         if (Utilities.isGoodStr(id))
         {
             mn = m_varNodes.get(id);
             // bail out if id not defined yet.
             if (mn == null)
             {
                 return(false);
             }
             if (mn.node != null)
             {
                 context = mn.node;
             }
         }
         return(glimpse(context, appPath, guiPath, property, expect));
     }
     return(false);
 }
示例#9
0
		/// <summary>
		/// ValueOrChild determines if the ah's value should be macthed or
		/// if a child should be matched.
		/// </summary>
		/// <param name="nextGP">The next path step beyond this ah</param>
		/// <param name="ah">The accessibility object currently considered</param>
		/// <param name="visitor">null or the object providing methods that are called when steps are found.</param>
		/// <returns>An AccessibilityHelper if checking for a value, otherwise null</returns>
		public AccessibilityHelper ValueOrChild(GuiPath nextGP, AccessibilityHelper ah, IPathVisitor visitor)
		{
			bool result = false;
			if (nextGP.Role == AccessibleRole.Alert)
			{ // check if the name is a regular expression
				if (nextGP.Name != null && nextGP.Name.StartsWith("rexp#"))
				{
					Regex rx = new Regex(nextGP.Name.Substring(5));
					result = rx.IsMatch(ah.Value);
					Log log = Log.getOnly();
					log.writeElt("ValueOrChild");
					log.writeAttr("pattern", nextGP.Name.Substring(5));
					log.writeAttr("to", ah.Value);
					log.writeAttr("result", result.ToString());
					log.endElt();
				}
				else result = nextGP.Name == ah.Value; // match the value to the next path's name
				if (!result)
				{ // it didn't match, so the search failed
					if (visitor != null) visitor.notFound(nextGP);
					return null;
				}
			}
			if (result) return ah;
			return ah.SearchPath(nextGP, visitor); // continue on the path
		}
示例#10
0
		/// <summary>
		/// Searches the path depth-first.
		/// Nth is not used - if it were, it could get a sibling, ancestor or child.
		/// </summary>
		/// <param name="path">The path to search. Each step in the path contains an
		/// Accessibility name and Accessibility role of the matching gui object.</param>
		/// <param name="visitor">null or the object providing methods that are called when steps are found.</param>
		/// <returns>A new <see cref="AccessibilityHelper"/> object that wraps the matched
		/// window.</returns>
		private AccessibilityHelper SearchPathByDepth(GuiPath path, IPathVisitor visitor)
		{
			Log log = Log.getOnly();
			log.writeElt("SearchPathByDepth");
			log.writeAttr("from", this.Role + ":" + this.Name);
			log.writeAttr("to", path.toString());
			log.endElt();

			if (ChildCount <= 0)
			{
				if (visitor != null) visitor.notFound(path);
				return null;
			}
			AccessibilityHelper ah = null;
			// try the client first if there is one, but don't visit it
			AccessibilityHelper client = FindClient();
			if (client != null) ah = client.SearchPathByDepth(path, visitor);
			if (ah != null)
			{
				log.endElt(); // end head element
				return ah;
			}

			// Rats!! It wasn't below the client, the caller wants some system widget or something
			foreach (AccessibilityHelper child in this)
			{
				if (child == null || child.Equals(client))
					continue;
				// does this object match?
				// treat name == "" and child.Name == null as a match
				// Note: a null string shows as "" in the debugger!
				log.writeElt("head");
				log.writeElt("SearchPathByDepth");
				log.writeAttr("path", path.toString());
				log.writeAttr("child", child.Role + ":" + child.Name);
				log.endElt();
				if (MatchNameAndRole(path.Name, path.Role, child))
				{
					if (path.Next != null)
					{
						if (visitor != null) visitor.visitNode(child); // allow processing of this ah by caller
						log.endElt(); // end head element
						return ValueOrChild(path.Next, child, visitor);
					}
				}
				// if not a "real" object, a child takes on it's parent's
				// attributes, so it appears to have the same # of children.
				// The first child is always the first child, so you get
				// infinite recursion on it if you don't check "realness".
				if (child.m_fRealAccessibleObject && child.ChildCount > 0)
				{
					// look through the child objects
					ah = child.SearchPathByDepth(path, visitor);
					if (ah != null)
					{
						if (path.Next != null)
						{
							if (visitor != null) visitor.visitNode(ah); // allow processing of this ah by caller
							log.endElt(); // end head element
							return ValueOrChild(path.Next, ah, visitor);
						}
						log.endElt(); // end head element
						return ah;
					}
				}
			}
			if (visitor != null) visitor.notFound(path);
			log.endElt(); // end head element
			return null;
		}
示例#11
0
		/// <summary>
		/// The search is almost breadth-first:
		/// The first step in the path has an unknown sibling index and its role is not 'value'.
		/// If this ah has children of the step type, they are searched until all the
		/// subsequent steps find matching ah's. This search substitutes its own visitor
		/// that records the successful path ah's. Upon success, a sibling index is assigned to
		/// the variable named in the step, the non-terminal subpath ah's are exposed
		/// to the caller's visitor in turn and this method returns the last ah on the path.
		/// Subpath steps are searched for depending on their content.
		/// </summary>
		/// <param name="path">The path to search. Each step in the path contains an
		/// Accessibility name and Accessibility role of the matching gui object.</param>
		/// <param name="visitor">null or the object providing methods that are called when steps are found.</param>
		/// <returns>A new <see cref="AccessibilityHelper"/> object that wraps the matched
		/// window.</returns>
		private AccessibilityHelper SearchPathForIndex(GuiPath path, IPathVisitor visitor)
		{
			Log log = Log.getOnly();
			log.writeElt("head");
			log.writeElt("SearchPathForIndex");
			log.writeAttr("from", this.Role + ":" + this.Name);
			log.writeAttr("to", path.toString());
			log.endElt();
			if (ChildCount <= 0)
			{
				if (visitor != null) visitor.notFound(path);
				return null;
			}
			AccessibilityHelper ah = null;
			int nnum = 0;
			int index = 0;
			string parentName = null;
			if (path.Prev != null) parentName = path.Prev.Name;
			foreach (AccessibilityHelper child in this)
			{
				if (child == null) continue;
				log.writeElt("SearchPathForIndex");
				log.writeAttr("child", (++nnum));
				log.writeAttr("path", ah.Role + ":" + ah.Name);
				// not supposed to be a value!
				if (path.Next != null && path.Next.Role == AccessibleRole.Alert)
					log.writeAttr("bad-value", ah.Value);
				log.endElt();
				if (MatchNameAndRole(path.Name, path.Role, child))
				{ // this is a candidate for matching the path
					++index; // the first one is 1
					if (path.Next != null)
					{  // This method won't allow for caller's visitors on candidate nodes until proved needed
						TrackingVisitor tv = new TrackingVisitor(); // tv may need to open sub menus
						if (tv != null) tv.visitNode(child); // add child to node list to visit later if this is the right path
						ah = ValueOrChild(path.Next, child, tv);
						if (ah != null)
						{   // the subpath was matched to the end
							if (path.VarId != null && path.VarId != "")
							{  // Create and execute a variable to record the index
					//			Var var = new Var();
					//			var.Id = path.VarId;
					//			var.Set = System.Convert.ToString(index); // 1 based count
					//			var.Execute(); // puts the var in the TestState hash
								// don't set path.Nth = index since it might change if the path is used again
							}
							// let the caller's visitor tend to all the path ah's
							if (visitor != null)
							{
								if (tv != null) tv.StepDownPath(visitor);
							}
							log.endElt(); // end head element
							return ah;
						}
					}
				}
				else if (parentName != null && child.Role == AccessibleRole.Client && child.Name == parentName)
				{ // try the client instead
					ah = child.SearchPathForIndex(path, visitor);
					if (ah != null)
					{
						log.endElt(); // end head element
						return ah; // found it
					}
				}
			}
			if (visitor != null) visitor.notFound(path);
			log.endElt(); // end head element
			return null;
		}
示例#12
0
		/// <summary>
		/// The search is almost breadth-first:
		/// The first step in the path is searched breath-first.
		/// Subsequent path steps are searched for depending on their content.
		/// The Accessibility helper representing the step is returned it is found and is the last step.
		/// Otherwise, the search continues with the next path step or if not found, null is returned.
		/// If the first step is found, the visitor is applied before continuing to the next step.
		/// </summary>
		/// <param name="path">The path to search. Each step in the path contains an
		/// Accessibility name and Accessibility role of the matching gui object.</param>
		/// <param name="visitor">null or the object providing methods that are called when steps are found.</param>
		/// <returns>A new <see cref="AccessibilityHelper"/> object that wraps the matched
		/// window.</returns>
		private AccessibilityHelper SearchPathByBreadth(GuiPath path, IPathVisitor visitor)
		{
			if (ChildCount <= 0 || path.Nth <= 0)
			{
				if (visitor != null) visitor.notFound(path);
				return null;
			}
			int place = path.Nth;
			Log log = Log.getOnly();
			log.writeElt("SearchPathByBreadth");
			log.writeAttr("from", this.Role + ":" + this.Name);
			log.writeAttr("to", path.toString());
			log.endElt();
			ArrayList lists = new ArrayList(); // list of child lists
			ArrayList nodes = MakeChildList(this);
			lists.Add(nodes);
			// Examine the first node in the list, dropping it after examination
			// and adding its children to the end if prudent.
			log.writeElt("head");
			int lnum = 0;
			while (lists.Count > 0)
			{
				int count = 0; // reset match count
				nodes = (ArrayList)lists[0];
				int nnum = 0; ++lnum;
				while (nodes.Count > 0)
				{
					AccessibilityHelper ah = (AccessibilityHelper)nodes[0];
					log.writeElt("SearchPathByBreadth");
					log.writeAttr("child", lnum + ":" + (++nnum));
					log.writeAttr("path", ah.Role + ":" + ah.Name);
					if (path.Next != null && path.Next.Role == AccessibleRole.Alert)
						log.writeAttr("value", ah.Value);
					log.endElt();

					if (MatchNameAndRole(path.Name, path.Role, ah))
					{ // this is the only way to return
						if (++count >= place)
						{ // Found this step, keep stepping along the path
							if (path.Next != null)
							{
								if (visitor != null) visitor.visitNode(ah); // allow processing of this ah by caller
								ah = ValueOrChild(path.Next, ah, visitor);
							}
							log.endElt(); // end head element
							return ah;
						}
					}
					if (ah.m_fRealAccessibleObject && ah.ChildCount > 0)
						lists.Add(MakeChildList(ah));
					nodes.RemoveAt(0); // when 0 is removed, all indices slide down 1
				}
				lists.RemoveAt(0); // when 0 is removed, all indices slide down 1
			}
			if (visitor != null) visitor.notFound(path);
			log.endElt(); // end head element
			return null;
		}
示例#13
0
		/// <summary>
		/// Finds the accessible object at the end of the path with optional place and varId from
		/// which it creates a variable object to hold the sibling number of the object found.
		/// An optional path visitor allows the caller to do something with intermediate steps
		/// along the path for debugging or interface control.
		/// The value of a node can be specified via a path step like "value:whatever the value is".
		/// When place is greater than 0, the search is breadth-first.
		/// Generally, clients need not be included in the "path".
		/// When place is 0, a value must be the leaf node of the path
		/// as there is typically some repeated structure
		/// like a table, row and cell with value. The structure is
		/// traversed repeatedly until the leaf node with the value is found.
		/// When place is negative, depth-first search is used. It tends to be
		/// the slowest way to search the Accessibility tree.
		/// </summary>
		/// <param name="path">The path to search. Each step in the path contains an
		/// Accessibility name and Accessibility role of the matching gui object.</param>
		/// <param name="visitor">null or the object providing methods that are called when steps are found.</param>
		/// <returns>A new <see cref="AccessibilityHelper"/> object that wraps the object found at the end of the path.</returns>
		public AccessibilityHelper SearchPath(GuiPath path, IPathVisitor visitor)
		{
			if (path == null) return null;

			// three cases based on Nth
			if (path.Nth > 0)
			{ // psuedo breadth-first
				return this.SearchPathByBreadth(path, visitor);
			}
			if (path.Nth == 0)
			{ // loop over FindChildBreadth to find a sibling index to set a var with varId
				return this.SearchPathForIndex(path, visitor);
			}
			else if (path.Nth < 0)
			{ // depth-first
				return this.SearchPathByDepth(path, visitor);
			}
			return null;
		}
示例#14
0
 /// <summary>
 /// Method to apply when a path node is not found.
 /// No path above it and none below will call this method;
 /// only the one not found.
 /// </summary>
 /// <param name="path">The path step last tried.</param>
 public void notFound(GuiPath path)
 {
     // do nothing
 }
示例#15
0
 /// <summary>
 /// Method to apply when a path node is not found.
 /// No path above it and none below will call this method;
 /// only the one not found.
 /// </summary>
 /// <param name="path">The path step last tried.</param>
 public void notFound(GuiPath path)
 {
     // do nothing
 }