package ca.cmput301f13t03.adventure_datetime.view.treeView; import android.content.res.Resources; import android.graphics.Canvas; import android.util.Log; import ca.cmput301f13t03.adventure_datetime.model.Choice; import ca.cmput301f13t03.adventure_datetime.model.StoryFragment; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Class that handles positioning of elements * @author Jesse */ class NodeGrid { private static final String TAG = "NODE_GRID"; private ArrayList<GridSegment> m_segments = new ArrayList<GridSegment>(); private ArrayList<FragmentConnection> m_connections = new ArrayList<FragmentConnection>(); private ArrayList<FragmentNode> m_nodes = new ArrayList<FragmentNode>(); ConnectionPlacer m_connectionPlacer = null; private StoryFragment m_selectedFrag = null; private FragmentNode m_selectedNode = null; private Resources m_res = null; private FragmentNode m_headNode = null; private Lock m_syncLock = new ReentrantLock(); private Map<UUID, StoryFragment> m_fragments = null; private UUID m_headFragmentId = null; private volatile boolean m_reloadView = false; public NodeGrid(Resources res) { m_res = res; } public void Draw(Canvas surface, Camera camera) { if(m_syncLock.tryLock()) { try { // early out, just in case the threads haven't yet // set up the fragments if(m_fragments == null) { return; } if(m_reloadView) { RebuildView(); FragmentNode headNode = GetTopLevelFragment(m_headFragmentId); camera.LookAt(headNode.x + headNode.width / 2, headNode.y + headNode.height / 2); } for(FragmentConnection connection : m_connections) { connection.Draw(surface, camera); } for(FragmentNode frag : m_nodes) { frag.Draw(surface, camera); } } finally { m_syncLock.unlock(); } } } public void RefreshView() { try { m_syncLock.lock(); for(FragmentNode node : m_nodes) { node.RefreshContents(); } } finally { m_syncLock.unlock(); } } public void AddChoice(StoryFragment origin, Choice choice) { try { m_syncLock.lock(); FragmentConnection newConnection = new FragmentConnection( origin.getFragmentID(), choice.getTarget(), m_res); FragmentNode originNode = GetNode(origin.getFragmentID()); FragmentNode targetNode = GetNode(choice.getTarget()); if(originNode != null && targetNode != null) { m_connectionPlacer.PlaceConnection(newConnection, originNode, targetNode); m_connections.add(newConnection); } } finally { m_syncLock.unlock(); } } public void RemoveChoice(StoryFragment origin, Choice choice) { try { m_syncLock.lock(); int toDelete = 0; int index = 0; UUID originId = origin.getFragmentID(); UUID targetId = choice.getTarget(); for(FragmentConnection connection : m_connections) { if( connection.GetOrigin().equals(originId) && connection.GetTarget().equals(targetId)) { toDelete = index; break; } ++index; } if(toDelete < m_connections.size()) { m_connections.remove(toDelete); } } finally { m_syncLock.unlock(); } } private FragmentNode GetNode(UUID fragId) { FragmentNode result = null; for(FragmentNode node : m_nodes) { if(node.GetFragment().getFragmentID().equals(fragId)) { result = node; break; } } return result; } private void RebuildView() { // clear the list of segments as we rebuild m_segments.clear(); m_connections.clear(); m_nodes.clear(); m_connectionPlacer = null; SetupNodes(m_fragments); SetupConnections(); m_reloadView = false; SelectFragment(m_selectedFrag); } /** * Set the fragments that are to be displayed by this component */ public void SetFragments(Map<UUID, StoryFragment> fragments, UUID headFragmentId) { m_syncLock.lock(); try { m_headFragmentId = headFragmentId; m_fragments = fragments; m_reloadView = true; } finally { m_syncLock.unlock(); } } public void SelectFragment(StoryFragment frag) { if(frag == null) return; if(m_selectedNode != null) { m_selectedNode.SetIsSelected(false); } m_selectedFrag = frag; if(m_nodes != null) { for(FragmentNode node : m_nodes) { if(node.GetFragment().getFragmentID().equals(frag.getFragmentID())) { m_selectedNode = node; m_selectedNode.SetIsSelected(true); } } } } public FragmentNode GetNodeAtLocation(int x, int y) { FragmentNode result = null; // ya, not the most efficent way to do it, but it works // would rather use a BSP tree, but that is just overkill... for(FragmentNode m_node : m_nodes) { if(m_node.Contains(x, y)) { result = m_node; } } return result; } private FragmentNode GetTopLevelFragment(UUID headId) { if(m_headNode == null || !(m_headNode.GetFragment().getFragmentID().equals(headId))) { for(FragmentNode node : m_nodes) { if(node.GetFragment().getFragmentID().equals(headId)) { m_headNode = node; break; } } } return m_headNode; } private void SetupNodes(Map<UUID, StoryFragment> fragsMap) { NodePlacer nodePlacer = new NodePlacer(); Set<UUID> placedFragments = new HashSet<UUID>(); Set<StoryFragment> notPlacedFragments = new HashSet<StoryFragment>(); Set<UUID> candidates = new HashSet<UUID>(); notPlacedFragments.addAll(fragsMap.values()); candidates.add(m_headFragmentId); while(!notPlacedFragments.isEmpty()) { // place the head node StoryFragment headFrag = SelectFragment(notPlacedFragments, candidates); AddNode(nodePlacer, notPlacedFragments, headFrag, placedFragments, candidates); // construct a list of nodes to place based upon the head node Set<StoryFragment> linkedFragments = GetLinkedFragments(headFrag, fragsMap); // place all linked nodes while(!linkedFragments.isEmpty()) { StoryFragment frag = SelectFragment(linkedFragments, candidates); linkedFragments.remove(frag); if(!placedFragments.contains(frag.getFragmentID())) { AddNode(nodePlacer, notPlacedFragments, frag, placedFragments, candidates); } } } assert(notPlacedFragments.size() == 0); this.m_segments = nodePlacer.GetSegments(); } private void AddNode( NodePlacer nodePlacer, Set<StoryFragment> notPlacedFragments, StoryFragment frag, Set<UUID> placedFragments, Set<UUID> candidates) { FragmentNode nextNode = new FragmentNode(frag, m_res); nodePlacer.PlaceFragment(nextNode); notPlacedFragments.remove(frag); placedFragments.add(frag.getFragmentID()); m_nodes.add(nextNode); for(Choice c : frag.getChoices()) { candidates.add(c.getTarget()); } } private StoryFragment SelectFragment(Set<StoryFragment> unPlaced, Set<UUID> criteria) { StoryFragment result = null; for(StoryFragment frag : unPlaced) { if(criteria.contains(frag.getFragmentID())) { result = frag; break; } } if(result == null) { result = unPlaced.iterator().next(); } return result; } private void SetupConnections() { Map<UUID, FragmentNode> lookupList = new HashMap<UUID, FragmentNode>(); // construct the lookup map for(FragmentNode node : m_nodes) { lookupList.put(node.GetFragment().getFragmentID(), node); } if(m_connectionPlacer == null) { m_connectionPlacer = new ConnectionPlacer(this.m_segments, GridSegment.GRID_SIZE); } // now iterate over each fragment node and connect it with its choices for(FragmentNode node : m_nodes) { List<Choice> links = node.GetFragment().getChoices(); for(Choice choice : links) { UUID key = choice.getTarget(); // lookup the node if(lookupList.containsKey(key)) { FragmentConnection connection = new FragmentConnection( node.GetFragment().getFragmentID(), choice.getTarget(), m_res); m_connectionPlacer.PlaceConnection(connection, node, lookupList.get(key)); this.m_connections.add(connection); } else { // What the hell? How could there be a choice without // a node? Log.e(TAG, "Choice with no associated node encountered! Discarding choice."); } } } } private Set<StoryFragment> GetLinkedFragments(StoryFragment head, Map<UUID, StoryFragment> allFrags) { Set<StoryFragment> linkedFrags = new TreeSet<StoryFragment>(); List<Choice> links = new ArrayList<Choice>(head.getChoices()); if(links != null && !links.isEmpty()) { do { Choice link = links.get(0); assert(allFrags.containsKey(link.getTarget())); StoryFragment frag = allFrags.get(link.getTarget()); // if we don't already have it then add it to the list if(frag != null && !linkedFrags.contains(frag)) { linkedFrags.add(frag); links.addAll(frag.getChoices()); } links.remove(0); }while(!links.isEmpty()); } return linkedFrags; } }