diff --git a/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java b/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java index 692a572..088a9cd 100644 --- a/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java +++ b/src/com/gpl/rpg/atcontentstudio/ATContentStudio.java @@ -5,10 +5,14 @@ import java.awt.Toolkit; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; +import prefuse.data.expression.parser.ExpressionParser; + import com.gpl.rpg.atcontentstudio.model.Workspace; import com.gpl.rpg.atcontentstudio.ui.StudioFrame; import com.gpl.rpg.atcontentstudio.ui.WorkerDialog; @@ -30,6 +34,8 @@ public class ATContentStudio { ConfigCache.init(); + Logger.getLogger(ExpressionParser.class.getName()).setLevel(Level.OFF); + try { String laf = ConfigCache.getFavoriteLaFClassName(); if (laf == null) laf = UIManager.getSystemLookAndFeelClassName(); diff --git a/src/com/gpl/rpg/atcontentstudio/model/GameSource.java b/src/com/gpl/rpg/atcontentstudio/model/GameSource.java index 15dcefb..72c070e 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/GameSource.java +++ b/src/com/gpl/rpg/atcontentstudio/model/GameSource.java @@ -31,6 +31,7 @@ import com.gpl.rpg.atcontentstudio.model.maps.Worldmap; import com.gpl.rpg.atcontentstudio.model.maps.WorldmapSegment; import com.gpl.rpg.atcontentstudio.model.sprites.SpriteSheetSet; import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet; +import com.gpl.rpg.atcontentstudio.model.tools.WriterModeDataSet; import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; public class GameSource implements ProjectTreeNode, Serializable { @@ -44,6 +45,7 @@ public class GameSource implements ProjectTreeNode, Serializable { public transient TMXMapSet gameMaps; public transient SpriteSheetSet gameSprites; public transient Worldmap worldmap; + public transient WriterModeDataSet writerModeDataSet; private transient SavedSlotCollection v; public static enum Type { @@ -86,6 +88,9 @@ public class GameSource implements ProjectTreeNode, Serializable { readResourceList(); } } + if (type == Type.created) { + this.writerModeDataSet = new WriterModeDataSet(this); + } this.gameData = new GameDataSet(this); this.gameMaps = new TMXMapSet(this); this.gameSprites = new SpriteSheetSet(this); @@ -95,6 +100,9 @@ public class GameSource implements ProjectTreeNode, Serializable { v.add(gameMaps); v.add(gameSprites); v.add(worldmap); + if (type == Type.created) { + v.add(writerModeDataSet); + } } public void readResourceList() { diff --git a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java index c230057..3d60dc5 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java +++ b/src/com/gpl/rpg/atcontentstudio/model/gamedata/Dialogue.java @@ -247,7 +247,7 @@ public class Dialogue extends JSONElement { case spawnAll: case removeSpawnArea: case deactivateSpawnArea: - reward.map = proj.getMap(reward.map_name); + reward.map = reward.map_name != null ? proj.getMap(reward.map_name) : null; break; case actorCondition: reward.reward_obj = proj.getActorCondition(reward.reward_obj_id); @@ -315,6 +315,11 @@ public class Dialogue extends JSONElement { if (rclone.reward_obj != null) { rclone.reward_obj.addBacklink(clone); } + rclone.map = r.map; + rclone.map_name = r.map_name; + if (rclone.map != null) { + rclone.map.addBacklink(clone); + } clone.rewards.add(rclone); } } diff --git a/src/com/gpl/rpg/atcontentstudio/model/tools/WriterModeData.java b/src/com/gpl/rpg/atcontentstudio/model/tools/WriterModeData.java new file mode 100644 index 0000000..3b91cb8 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/tools/WriterModeData.java @@ -0,0 +1,251 @@ +package com.gpl.rpg.atcontentstudio.model.tools; + +import java.awt.Image; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.swing.tree.TreeNode; + +import com.gpl.rpg.atcontentstudio.model.GameDataElement.State; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.model.tools.WriterModeData.WriterDialogue; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class WriterModeData extends GameDataElement { + + public WriterModeDataSet parent; + + public Image npcIcon; + public WriterDialogue begin; + public Map dialogueThreads = new LinkedHashMap(); + public Map threadsNextIndex = new LinkedHashMap(); + + + public WriterModeData(WriterModeDataSet parent, String id_prefix){ + this.parent = parent; + this.begin = new WriterDialogue(); + begin.id_prefix = id_prefix; + begin.index = getNextIndex(id_prefix); + begin.text = ""; + } + + public WriterModeData(WriterModeDataSet parent, Map jsonObj) { + this.parent = parent; + this.begin = new WriterDialogue(jsonObj); + this.state = State.parsed; + } + + public int getNextIndex(String id_prefix) { + Integer index = threadsNextIndex.get(id_prefix); + if (index == null) index = 0; + while (getProject().getDialogue(id_prefix+index) != null) { + index++; + } + threadsNextIndex.put(id_prefix, index + 1); + return index; + } + + + public abstract class WriterNode { + public String text; + + public abstract String getTitle(); + public abstract Map toJson(); + + } + + public class WriterDialogue extends WriterNode { + public String id_prefix; + public int index; + public List replies = new LinkedList(); + + + public WriterDialogue() {} + + public WriterDialogue(String id_prefix) { + text = ""; + this.id_prefix = id_prefix; + index = getNextIndex(id_prefix); + } + + @Override + public String getTitle() { + return "Dialogue "+id_prefix+index; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Map toJson() { + Map dialogueJson = new HashMap(); + dialogueJson.put("id_prefix", id_prefix); + dialogueJson.put("index", index); + dialogueJson.put("text", text); + if (!replies.isEmpty()) { + List repliesJson = new ArrayList(); + for (WriterReply reply : replies) { + repliesJson.add(reply.toJson()); + } + dialogueJson.put("replies", repliesJson); + } + return dialogueJson; + } + + public WriterDialogue(Map json) { + this.index = Integer.parseInt((String) json.get("index")); + this.id_prefix = (String) json.get("id_prefix"); + this.text = (String) json.get("text"); + List repliesJson = (List) json.get("replies"); + for (Object rJson : repliesJson) { + this.replies.add(new WriterReply(this, (Map)rJson)); + } + } + + } + + public class SpecialDialogue extends WriterDialogue {} + public class SelectorDialogue extends SpecialDialogue {} + public class ShopDialogue extends SpecialDialogue {} + public class FightDialogue extends SpecialDialogue {} + public class EndDialogue extends SpecialDialogue {} + + public class WriterReply extends WriterNode { + public WriterDialogue parent; + public WriterDialogue next_dialogue; + + public WriterReply() {} + + public WriterReply(WriterDialogue parent) { + this.parent = parent; + this.text = ""; + parent.replies.add(this); + } + + public WriterReply(WriterDialogue parent, Map json) { + this.parent = parent; + this.text = (String) json.get("text"); + if (json.containsKey("next_dialogue")) { + next_dialogue = new WriterDialogue((Map) json.get("next_dialogue")); + } + } + + @Override + public String getTitle() { + return "Reply in "+parent.id_prefix+parent.index; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public Map toJson() { + Map replyJson = new HashMap(); + replyJson.put("text", text); + if (next_dialogue != null) { + replyJson.put("next_dialogue", next_dialogue.toJson()); + } + return replyJson; + } + + + } + + public class SpecialReply extends WriterReply { + + public SpecialReply(WriterDialogue parent) { + super(parent); + } + } + public class EmptyReply extends SpecialReply { + + public EmptyReply(WriterDialogue parent) { + super(parent); + } + } + + + + @Override + public String getDesc() { + return (this.state == State.modified ? "*" : "")+begin.id_prefix; + } + @Override + public Project getProject() { + return parent.getProject(); + } + + @Override + public Image getIcon() { + return DefaultIcons.getDialogueIcon(); + } + @Override + public Image getOpenIcon() { + return null; + } + @Override + public Image getClosedIcon() { + return null; + } + @Override + public Image getLeafIcon() { + return getIcon(); + } + @Override + public GameDataSet getDataSet() { + return null; + } + @Override + public void parse() { + // TODO + + } + @Override + public void link() { + //Useless here. + } + @Override + public GameDataElement clone() { + //TODO + return null; + } + @Override + public void elementChanged(GameDataElement oldOne, GameDataElement newOne) { + // Useless here. + + } + @Override + public String getProjectFilename() { + return WriterModeDataSet.DEFAULT_REL_PATH_IN_PROJECT; + } + + @Override + public void save() { + parent.save(); + } + @Override + public List attemptSave() { + List events = parent.attemptSave(); + if (events == null || events.isEmpty()) { + return null; + } + if (events.size() == 1 && events.get(0).type == SaveEvent.Type.alsoSave && events.get(0).target == this) { + save(); + return null; + } + return events; + } + + @SuppressWarnings("rawtypes") + public Map toJson() { + return begin.toJson(); + } + + +} diff --git a/src/com/gpl/rpg/atcontentstudio/model/tools/WriterModeDataSet.java b/src/com/gpl/rpg/atcontentstudio/model/tools/WriterModeDataSet.java new file mode 100644 index 0000000..dee8b1a --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/model/tools/WriterModeDataSet.java @@ -0,0 +1,233 @@ +package com.gpl.rpg.atcontentstudio.model.tools; + +import java.awt.Image; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Serializable; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.swing.tree.TreeNode; + +import org.json.simple.JSONArray; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.gpl.rpg.atcontentstudio.Notification; +import com.gpl.rpg.atcontentstudio.io.JsonPrettyWriter; +import com.gpl.rpg.atcontentstudio.model.GameDataElement.State; +import com.gpl.rpg.atcontentstudio.model.GameSource; +import com.gpl.rpg.atcontentstudio.model.GameSource.Type; +import com.gpl.rpg.atcontentstudio.model.GameDataElement; +import com.gpl.rpg.atcontentstudio.model.Project; +import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode; +import com.gpl.rpg.atcontentstudio.model.SaveEvent; +import com.gpl.rpg.atcontentstudio.model.gamedata.GameDataSet; +import com.gpl.rpg.atcontentstudio.ui.DefaultIcons; + +public class WriterModeDataSet implements ProjectTreeNode, Serializable { + + private static final long serialVersionUID = 5434504851883441971L; + public static final String DEFAULT_REL_PATH_IN_PROJECT = "writer.json"; + + + public GameSource parent; + public File writerFile; + + List writerModeDataList = new LinkedList(); + + public WriterModeDataSet(GameSource gameSource) { + this.parent = gameSource; + writerFile = new File(parent.baseFolder, DEFAULT_REL_PATH_IN_PROJECT); + parse(); + } + + @Override + public TreeNode getChildAt(int childIndex) { + return null; + } + + @Override + public int getChildCount() { + return writerModeDataList.size(); + } + + @Override + public TreeNode getParent() { + return parent; + } + + @Override + public int getIndex(TreeNode node) { + return writerModeDataList.indexOf(node); + } + + @Override + public boolean getAllowsChildren() { + return true; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public Enumeration children() { + return Collections.enumeration(writerModeDataList); + } + + @Override + public void childrenAdded(List path) { + path.add(0,this); + parent.childrenAdded(path); + } + + @Override + public void childrenChanged(List path) { + path.add(0,this); + parent.childrenChanged(path); + } + + @Override + public void childrenRemoved(List path) { + path.add(0,this); + parent.childrenRemoved(path); + } + + @Override + public void notifyCreated() { + childrenAdded(new ArrayList()); + } + + @Override + public String getDesc() { + return "Dialogue sketchs"; + } + + @Override + public Project getProject() { + return parent.getProject(); + } + + @Override + public GameDataSet getDataSet() { + return null; + } + + @Override + public Image getIcon() { + return DefaultIcons.getStdClosedIcon(); + } + + @Override + public Image getOpenIcon() { + return DefaultIcons.getStdOpenIcon(); + } + + @Override + public Image getClosedIcon() { + return DefaultIcons.getStdClosedIcon(); + } + + @Override + public Image getLeafIcon() { + return null; + } + + @Override + public Type getDataType() { + return parent.getDataType(); + } + + @Override + public boolean isEmpty() { + return writerModeDataList.isEmpty(); + } + + + @SuppressWarnings("rawtypes") + public void save() { + List dataToSave = new LinkedList(); + for (WriterModeData data : writerModeDataList) { + dataToSave.add(data.toJson()); + } + if (dataToSave.isEmpty() && writerFile.exists()) { + if (writerFile.delete()) { + Notification.addSuccess("File "+writerFile.getAbsolutePath()+" deleted."); + } else { + Notification.addError("Error deleting file "+writerFile.getAbsolutePath()); + } + + return; + } + StringWriter writer = new JsonPrettyWriter(); + try { + JSONArray.writeJSONString(dataToSave, writer); + } catch (IOException e) { + //Impossible with a StringWriter + } + String toWrite = writer.toString(); + try { + FileWriter w = new FileWriter(writerFile); + w.write(toWrite); + w.close(); + for (WriterModeData element : writerModeDataList) { + element.state = GameDataElement.State.saved; + } + Notification.addSuccess("Json file "+writerFile.getAbsolutePath()+" saved."); + } catch (IOException e) { + Notification.addError("Error while writing json file "+writerFile.getAbsolutePath()+" : "+e.getMessage()); + e.printStackTrace(); + } + } + + public List attemptSave() { + List events = new LinkedList(); + for (WriterModeData data : writerModeDataList) { + if (data.state == State.created || data.state == State.modified) { + events.add(new SaveEvent(SaveEvent.Type.alsoSave, data)); + } + } + return events; + } + + public void parse() { + if (!writerFile.exists()) return; + JSONParser parser = new JSONParser(); + FileReader reader = null; + try { + reader = new FileReader(writerFile); + List writerDataListJson = (List) parser.parse(reader); + for (Object obj : writerDataListJson) { + Map jsonObj = (Map)obj; + writerModeDataList.add(new WriterModeData(this, jsonObj)); + } + } catch (FileNotFoundException e) { + Notification.addError("Error while parsing JSON file "+writerFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (IOException e) { + Notification.addError("Error while parsing JSON file "+writerFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } catch (ParseException e) { + Notification.addError("Error while parsing JSON file "+writerFile.getAbsolutePath()+": "+e.getMessage()); + e.printStackTrace(); + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + +} diff --git a/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java b/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java index ba306b7..ea3d372 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/ProjectsTree.java @@ -236,6 +236,15 @@ public class ProjectsTree extends JPanel { addNextSeparator = false; } + if (actions.testWriter.isEnabled()) { + addNextSeparator = true; + popupMenu.add(new JMenuItem(actions.testWriter)); + } + if (addNextSeparator) { + popupMenu.add(new JSeparator()); + addNextSeparator = false; + } + if (konamiCodeEntered) { JMenuItem openTrainer = new JMenuItem("Start Andor's Trainer..."); popupMenu.add(openTrainer); diff --git a/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java index 035dcbd..a98686e 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/WorkspaceActions.java @@ -1,5 +1,7 @@ package com.gpl.rpg.atcontentstudio.ui; +import java.awt.BorderLayout; +import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; @@ -15,6 +17,7 @@ import java.util.Set; import javax.swing.Action; import javax.swing.JFileChooser; +import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.KeyStroke; import javax.swing.tree.TreePath; @@ -34,9 +37,11 @@ import com.gpl.rpg.atcontentstudio.model.gamedata.JSONElement; import com.gpl.rpg.atcontentstudio.model.maps.TMXMap; import com.gpl.rpg.atcontentstudio.model.maps.TMXMapSet; import com.gpl.rpg.atcontentstudio.model.saves.SavedGamesSet; +import com.gpl.rpg.atcontentstudio.model.tools.WriterModeData; import com.gpl.rpg.atcontentstudio.ui.tools.BeanShellView; import com.gpl.rpg.atcontentstudio.ui.tools.ItemsTableView; import com.gpl.rpg.atcontentstudio.ui.tools.NPCsTableView; +import com.gpl.rpg.atcontentstudio.ui.tools.writermode.WriterModeEditor; public class WorkspaceActions { @@ -314,6 +319,20 @@ public class WorkspaceActions { }; }; + public ATCSAction testWriter = new ATCSAction("Create dialogue sketch", "Test the Writer Mode"){ + public void actionPerformed(ActionEvent e) { + if (selectedNode == null || selectedNode.getProject() == null) return; + WriterModeData data = new WriterModeData(selectedNode.getProject().createdContent.writerModeDataSet, "test_"); + JFrame frame = new JFrame("Writer Mode tests"); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.getContentPane().setLayout(new BorderLayout()); + frame.getContentPane().add(new WriterModeEditor(data), BorderLayout.CENTER); + frame.setMinimumSize(new Dimension(250, 200)); + frame.pack(); + frame.setVisible(true); + }; + }; + List actions = new ArrayList(); public WorkspaceActions() { @@ -331,6 +350,7 @@ public class WorkspaceActions { actions.add(exportProject); actions.add(showAbout); actions.add(exitATCS); + actions.add(testWriter); selectionChanged(null, null); } diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java index fd58697..9996bd3 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/DialogueEditor.java @@ -109,15 +109,43 @@ public class DialogueEditor extends JSONElementEditor { private JSpinner requirementValue; private BooleanBasedCheckBox requirementNegated; + private DialogueGraphView dialogueGraphView; + public DialogueEditor(Dialogue dialogue) { super(dialogue, dialogue.getDesc(), dialogue.getIcon()); addEditorTab(form_view_id, getFormView()); addEditorTab(json_view_id, getJSONView()); - JPanel pane = new JPanel(); + addEditorTab(graph_view_id, createDialogueGraphView(dialogue)); + } + + public JPanel createDialogueGraphView(final Dialogue dialogue) { + final JPanel pane = new JPanel(); pane.setLayout(new BorderLayout()); - pane.add(new JScrollPane(new DialogueGraphView(dialogue, null)), BorderLayout.CENTER); - addEditorTab(graph_view_id, pane); + + dialogueGraphView = new DialogueGraphView(dialogue, null); + pane.add(dialogueGraphView, BorderLayout.CENTER); + + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS)); + JButton reloadButton = new JButton("Refresh graph"); + buttonPane.add(reloadButton, JideBoxLayout.FIX); + buttonPane.add(new JPanel(), JideBoxLayout.VARY); + pane.add(buttonPane, BorderLayout.NORTH); + + + reloadButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + pane.remove(dialogueGraphView); + dialogueGraphView = new DialogueGraphView(dialogue, null); + pane.add(dialogueGraphView, BorderLayout.CENTER); + pane.revalidate(); + pane.repaint(); + } + }); + + return pane; } public void insertFormViewDataField(final JPanel pane) { diff --git a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java index 57d810f..ce539a1 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/gamedataeditors/JSONElementEditor.java @@ -65,6 +65,15 @@ public abstract class JSONElementEditor extends Editor { editorTabs.put(id, editor); } + public void removeEditorTab(String id) { + if (id == null) return; + for (int i =0; i cells = new HashMap(); + + private Node nullNode = null; + private Edge pendingEdge = null; + private boolean edgePending = false; + + public WriterGraphView() { + super(new Visualization()); + loadGraph(); + nullNode = graph.addNode(); + nullNode.setBoolean(NULL_NODES, true); + + // add visual data groups + m_vis.addGraph(GRAPH, graph); + m_vis.setInteractive(EDGES, null, false); + + LabelRenderer nodeR = new MyLabelRenderer(LABEL); + nodeR.setHorizontalTextAlignment(prefuse.Constants.LEFT); + + EdgeRenderer edgeR = new EdgeRenderer(prefuse.Constants.EDGE_TYPE_LINE, prefuse.Constants.EDGE_ARROW_FORWARD); + + LabelRenderer edgeLabelR = new LabelRenderer(LABEL); + edgeLabelR.setRenderType(LabelRenderer.RENDER_TYPE_DRAW); + + DefaultRendererFactory drf = new DefaultRendererFactory(); + drf.setDefaultRenderer(nodeR); + drf.setDefaultEdgeRenderer(edgeR); + drf.add(new InGroupPredicate(EDGES_LABELS), edgeLabelR); + drf.add(new ColumnExpression(NULL_NODES), new NullRenderer()); + m_vis.setRendererFactory(drf); + DECORATOR_SCHEMA.setDefault(VisualItem.FILLCOLOR, ColorLib.gray(255)); + DECORATOR_SCHEMA.setDefault(VisualItem.STROKECOLOR, ColorLib.rgba(0, 0, 0, 0)); + DECORATOR_SCHEMA.setDefault(VisualItem.TEXTCOLOR, ColorLib.gray(0)); + m_vis.addDecorators(EDGES_LABELS, EDGES, DECORATOR_SCHEMA); + + // set up the visual operators + // first set up all the color actions + ColorAction nStrokeColor = new NodeStrokeColorAction(NODES, VisualItem.STROKECOLOR); +// nStrokeColor.setDefaultColor(ColorLib.gray(100)); +// nStrokeColor.add("_hover", ColorLib.rgb(255,100,100)); + StrokeAction nStroke = new StrokeAction(NODES); + + ColorAction nFill = new ColorAction(NODES, VisualItem.FILLCOLOR); + nFill.setDefaultColor(ColorLib.gray(255)); +// ColorAction nFill = new NPCPhraseColorAction(NODES, VisualItem.FILLCOLOR); +// + ColorAction eEdges = new ColorAction(EDGES, VisualItem.STROKECOLOR); + eEdges.setDefaultColor(ColorLib.gray(100)); + ColorAction eArrows = new ColorAction(EDGES, VisualItem.FILLCOLOR); + eArrows.setDefaultColor(ColorLib.gray(100)); +// ColorAction eEdgesLabels = new ConnectedEdgeColorAction(EDGES_LABELS, VisualItem.TEXTCOLOR); + +// StrokeAction eStroke = new EdgesStrokeAction(EDGES); + + FontAction aFont = new FontAction(); + ColorAction aFontColor = new ColorAction(NODES, VisualItem.TEXTCOLOR); + aFontColor.setDefaultColor(ColorLib.rgb(0, 0, 0)); + + // bundle the color actions + ActionList colors = new ActionList(Activity.INFINITY); + colors.add(nStrokeColor); + colors.add(nFill); + colors.add(nStroke); + colors.add(eEdges); + colors.add(eArrows); +// colors.add(eEdgesLabels); +// colors.add(eStroke); + colors.add(aFont); + colors.add(aFontColor); + colors.add(new RepaintAction()); + m_vis.putAction("colors", colors); + + // now create the main layout routine + ActionList layout = new ActionList();//Activity.INFINITY); + NodeLinkTreeLayout treeLayout = new NodeLinkTreeLayout(GRAPH, prefuse.Constants.ORIENT_LEFT_RIGHT, 120, 40, 40); + treeLayout.setLayoutAnchor(new Point2D.Double(25,300)); + layout.add(treeLayout); +// layout.add(new EdgesLabelDecoratorLayout(EDGES_LABELS)); + layout.add(new RepaintAction()); + m_vis.putAction("layout", layout); + + ActionList scrollToSelectedList = new ActionList(); + Action scrollToSelected = new ScrollToSelectedAction(false); + scrollToSelectedList.add(scrollToSelected); + m_vis.putAction("scrollToSelected", scrollToSelectedList); + + + ActionList scrollToSelectedAndEditList = new ActionList(); + Action scrollToSelectedAndEdit = new ScrollToSelectedAction(true); + scrollToSelectedAndEditList.add(scrollToSelectedAndEdit); + m_vis.putAction("scrollToSelectedAndEdit", scrollToSelectedAndEditList); + + + // set up the display + setSize(500,500); + pan(250, 250); + setHighQuality(true); +// addControlListener(new TooltipControl()); + addControlListener(new GraphInputControl()); + addControlListener(new WheelZoomControl()); + addControlListener(new ZoomControl()); + addControlListener(new PanControl()); + + // set things running + m_vis.run("colors"); + m_vis.run("layout"); + m_vis.run("scrollToSelected"); + + setFocusTraversalKeysEnabled(false); + } + + public void loadGraph() { + graph = new MyGraph(true, IS_TREE_EDGE); + + graph.addColumn(LABEL, String.class, ""); + graph.addColumn(ICON, Image.class, DefaultIcons.getNullifyIcon()); + graph.addColumn(TARGET, WriterModeData.WriterNode.class, null); + + graph.addColumn(IS_REPLY, boolean.class, false); + graph.addColumn(THREAD_START, boolean.class, false); + graph.addColumn(SELECTED, boolean.class, false); + graph.addColumn(IS_TREE_EDGE, boolean.class, true); + graph.addColumn(NULL_NODES, boolean.class, false); + + if (data != null && data.begin != null) { + selected = data.begin; + addDialogueNode(data.begin); + } + } + + public Node addDialogueNode(WriterModeData.WriterDialogue dialogue) { + if (cells.get(dialogue) == null) { + Node dNode = graph.addNode(); + cells.put(dialogue, dNode); + dNode.setString(LABEL, dialogue.text); + dNode.set(TARGET, dialogue); + + if (dialogue.index == 0) { + dNode.setBoolean(THREAD_START, true); + } + + Node rNode; + int i = 1; + for (WriterModeData.WriterReply reply : dialogue.replies) { + if (reply instanceof EmptyReply && reply.next_dialogue != null) { + if (cells.get(reply.next_dialogue) == null) { + rNode = addDialogueNode(reply.next_dialogue); + Edge e = graph.addEdge(dNode, rNode); + } else { + rNode = cells.get(reply.next_dialogue); + Edge e = graph.addEdge(dNode, rNode); + e.setBoolean(IS_TREE_EDGE, false); + } + } else { + if (cells.get(reply) == null) { + rNode = addReplyNode(reply); + Edge e = graph.addEdge(dNode, rNode); +// e.setString(LABEL, "#"+i++); + } else { + rNode = cells.get(reply); + Edge e = graph.addEdge(dNode, rNode); + e.setBoolean(IS_TREE_EDGE, false); + } + } + } + } + return cells.get(dialogue); + } + + public Node addReplyNode(WriterModeData.WriterReply reply) { + if (cells.get(reply) == null) { + Node rNode = graph.addNode(); + rNode.setBoolean(IS_REPLY, true); + cells.put(reply, rNode); + if (reply.text != null) { + rNode.setString(LABEL, reply.text); + rNode.set(TARGET, reply); + + if (reply.next_dialogue != null) { + if (cells.get(reply.next_dialogue) == null) { + Node dNode = addDialogueNode(reply.next_dialogue); + Edge e = graph.addEdge(rNode, dNode); + } else { + Node dNode = cells.get(reply.next_dialogue); + Edge e = graph.addEdge(rNode, dNode); + e.setBoolean(IS_TREE_EDGE, false); + } + } + } + } + return cells.get(reply); + } + + + class MyLabelRenderer extends LabelRenderer { + public MyLabelRenderer(String label) { + super(label); + } + + @Override + protected Image getImage(VisualItem item) { + return item.getBoolean(IS_REPLY) ? DefaultIcons.getHeroIcon() : DefaultIcons.getNPCIcon(); + } + + @Override + protected String getText(VisualItem item) { + return wordWrap(super.getText(item), 40); + } + + public String wordWrap(String in, int length) { + final String newline = "\n"; + //:: Trim + while(in.length() > 0 && (in.charAt(0) == '\t' || in.charAt(0) == ' ')) in = in.substring(1); + //:: If Small Enough Already, Return Original + if(in.length() < length) return in; + //:: If Next length Contains Newline, Split There + if(in.substring(0, length).contains(newline)) return in.substring(0, in.indexOf(newline)).trim() + newline + wordWrap(in.substring(in.indexOf("\n") + 1), length); + //:: Otherwise, Split Along Nearest Previous Space/Tab/Dash + int spaceIndex = Math.max(Math.max( in.lastIndexOf(" ", length), in.lastIndexOf("\t", length)), in.lastIndexOf("-", length)); + //:: If No Nearest Space, Split At length + if(spaceIndex == -1) spaceIndex = length; + //:: Split + return in.substring(0, spaceIndex).trim() + newline + wordWrap(in.substring(spaceIndex), length); + } + } + + class NodeStrokeColorAction extends ColorAction { + + final int defaultColor = ColorLib.gray(100); + final int hoverColor = ColorLib.rgb(255,100,100); + final int selectedColor = ColorLib.rgb(100,100,255); + + public NodeStrokeColorAction(String group, String field) { + super(group, field); + } + + @Override + public int getColor(VisualItem item) { + if (item.get(TARGET) != null && item.get(TARGET) == selected) { + return selectedColor; + } + if (item.isHover()) { + return hoverColor; + } + return defaultColor; + } + } + + class GraphInputControl extends ControlAdapter { + @Override + public void itemClicked(VisualItem item, MouseEvent e) { + if (!edgePending) { + if (e.getClickCount() == 1) { + if (item.get(TARGET) != null) { + prevSelected = selected; + selected = (WriterModeData.WriterNode)item.get(TARGET); + } + } else if (e.getClickCount() == 2) { + if (item.get(TARGET) != null) { + + showEditorOnSelectedAt(e.getPoint()); + } + } + } + } + + @Override + public void mouseClicked(MouseEvent e) { + selected = null; + view.requestFocus(); + } + + @Override + public void keyReleased(KeyEvent e) { + if (edgePending && e.getKeyCode() == KeyEvent.VK_SHIFT) { + stopPendingEdge(); + } + KeyStroke event = KeyStroke.getKeyStrokeForEvent(e); + if (event.equals(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true))) { + selectAndScroll(nextNodeUp(selected)); + } else if (event.equals(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true))) { + selectAndScroll(nextNodeDown(selected)); + } else if (event.equals(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true)) || event.equals(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK, true))) { + selectAndScroll(nextNodeLeft(selected)); + } else if (event.equals(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true)) || event.equals(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0, true))) { + selectAndScroll(nextNodeRight(selected)); + } else if (event.equals(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true))) { + showEditorOnSelected(); + } else if (event.equals(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.ALT_DOWN_MASK, true))) { + createNextDefaultNode.actionPerformed(null); + } else if (event.equals(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK, true))) { + createContinueTalkingNode.actionPerformed(null); + } + } + + @Override + public void itemKeyReleased(VisualItem item, KeyEvent e) { + keyReleased(e); + } + + @Override + public void keyPressed(KeyEvent e) { + if (selected != null && e.getKeyCode() == KeyEvent.VK_SHIFT) { + startPendingEdge(); + } + } + + @Override + public void itemKeyPressed(VisualItem item, KeyEvent e) { + keyPressed(e); + } + + @Override + public void mouseMoved(MouseEvent e) { + if (edgePending) { + Point p = e.getPoint(); + Point2D p2 = getAbsoluteCoordinate(p, null); + m_vis.getVisualItem(NODES, nullNode).setX(p2.getX()); + m_vis.getVisualItem(NODES, nullNode).setY(p2.getY()); + m_vis.run("colors"); + revalidate(); + repaint(); + disposeOverlay(); + } + } + + @Override + public void itemMoved(VisualItem item, MouseEvent e) { + mouseMoved(e); + } + + } + + + public void startPendingEdge() { + pendingEdge = graph.addEdge(cells.get(selected), nullNode); + edgePending = true; + m_vis.run("colors"); + m_vis.run("layout"); + revalidate(); + repaint(); + } + + + public void stopPendingEdge() { + graph.removeEdge(pendingEdge); + pendingEdge = null; + edgePending = false; + m_vis.run("colors"); + m_vis.run("layout"); + revalidate(); + repaint(); + } + + static final String disposeEditorString = "disposeEditor"; + final AbstractAction disposeEditor = new AbstractAction("Dispose Editor") { + private static final long serialVersionUID = 6640035253411399809L; + + @Override + public void actionPerformed(ActionEvent e) { + disposeOverlay(); + } + }; + + + static final String commitEditAndDisposeEditorString = "commitEditAndDisposeEditor"; + final AbstractAction commitEditAndDisposeEditor = new AbstractAction("Commit Edit") { + private static final long serialVersionUID = 8039766217709796328L; + + @Override + public void actionPerformed(ActionEvent e) { + if (area == null) return; + selected.text = area.getText(); + cells.get(selected).set(LABEL, selected.text); + m_vis.run("colors"); + revalidate(); + repaint(); + disposeOverlay(); + } + }; + + private void commitAreaText() { + selected.text = area.getText(); + cells.get(selected).set(LABEL, selected.text); + } + + static final String createNextDefaultNodeString = "createNextDefaultNode"; + final AbstractAction createNextDefaultNode = new AbstractAction("Create next default") { + private static final long serialVersionUID = 1658086056088672748L; + + @Override + public void actionPerformed(ActionEvent e) { + WriterNode newWrNode = null; + Node newNode = null; + if (selected instanceof WriterDialogue) { + newWrNode = data.new WriterReply((WriterDialogue) selected); + newNode = addReplyNode(((WriterReply)newWrNode)); + } else if (selected instanceof WriterReply) { + if (((WriterReply)selected).next_dialogue != null) { + newWrNode = ((WriterReply)selected).next_dialogue; + newNode = cells.get(newWrNode); + } else { + newWrNode = data.new WriterDialogue(((WriterReply)selected).parent.id_prefix); + ((WriterReply)selected).next_dialogue = ((WriterDialogue)newWrNode); + newNode = addDialogueNode(((WriterDialogue)newWrNode)); + } + } + Edge edge = graph.addEdge(cells.get(selected), newNode); + setSelected(newWrNode); + + m_vis.run("colors"); + m_vis.run("layout"); + m_vis.run("scrollToSelectedAndEdit"); + + revalidate(); + repaint(); + } + }; + + static final String commitAndCreateNextDefaultNodeString = "commitAndCreateNextDefaultNode"; + final AbstractAction commitAndCreateNextDefaultNode = new AbstractAction("Commit And Create next default") { + private static final long serialVersionUID = 1658086056088672748L; + + @Override + public void actionPerformed(ActionEvent e) { + commitAreaText(); + createNextDefaultNode.actionPerformed(e); + } + }; + + + static final String createContinueTalkingNodeString = "createContinueTalkingNode"; + final AbstractAction createContinueTalkingNode = new AbstractAction("Create next phrase without reply") { + private static final long serialVersionUID = 1658086056088672748L; + + @Override + public void actionPerformed(ActionEvent e) { + commitAreaText(); + WriterDialogue newWrNode = null; + Node newNode = null; + if (selected instanceof WriterDialogue) { + EmptyReply temp = data.new EmptyReply((WriterDialogue) selected); + newWrNode = data.new WriterDialogue(((WriterDialogue) selected).id_prefix); + temp.next_dialogue = newWrNode; + + newNode = addDialogueNode(newWrNode); + Edge edge = graph.addEdge(cells.get(selected), newNode); + setSelected(newWrNode); + } + + m_vis.run("colors"); + m_vis.run("layout"); + m_vis.run("scrollToSelectedAndEdit"); + + revalidate(); + repaint(); + } + }; + + class ScrollToSelectedAction extends Action { + + final boolean openEditor; + + public ScrollToSelectedAction(boolean openEditor) { + super(); + this.openEditor = openEditor; + } + + @Override + public void run(double frac) { + new Thread() { + @Override + public void run() { + if (selected == null) return; + VisualItem newItem = WriterGraphView.this.m_vis.getVisualItem(NODES, cells.get(selected)); + if (prevSelected != null) { + VisualItem prevItem = m_vis.getVisualItem(NODES, cells.get(prevSelected)); + Point2D target = getScreenCoordinates(new Point2D.Double(prevItem.getX(), prevItem.getY()), null); + animatePan(prevItem.getX() - newItem.getX(), prevItem.getY() - newItem.getY(), 200); + if (openEditor)showEditorOnSelectedAt(target); + } else { + animatePanToAbs(new Point2D.Double(newItem.getX(), newItem.getY()), 200); + } + } + }.start(); + } + }; + + + private void showEditorOnSelected() { + if (selected == null) return; + Node selNode = cells.get(selected); + if (selNode != null) { + VisualItem vItem = m_vis.getVisualItem(NODES, selNode); + if (vItem != null) { + showEditorOnSelectedAt(getScreenCoordinates(new Point2D.Double(vItem.getX(), vItem.getY()),null)); + } + } + } + + JTextArea area; + private void showEditorOnSelectedAt(final Point2D p) { + if (overlay != null) disposeOverlay(); + + //System.out.println(p); + area = new JTextArea(selected.text); + JInternalFrame frame = new JInternalFrame(selected.getTitle(), true); + frame.getContentPane().setLayout(new BorderLayout()); + JPanel pane = new JPanel(); + pane.setLayout(new BorderLayout()); + pane.add(new JScrollPane(area)); + frame.setSize(250, 80); + frame.setLocation(new Point((int)p.getX(), (int)p.getY())); + frame.setVisible(true); + frame.getContentPane().add(pane, BorderLayout.CENTER); + ((BasicInternalFrameUI)frame.getUI()).getNorthPane().remove(0); + + JButton commit = new JButton(commitEditAndDisposeEditor); + commit.setToolTipText("Save text and close editor (Ctrl + Enter)"); + + JButton cancel = new JButton(disposeEditor); + cancel.setToolTipText("Discard changes and close editor (Escape)"); + + + view.add(frame); + overlay = frame; + frame.requestFocus(); + area.requestFocus(); + area.addFocusListener(new FocusListener() { + @Override + public void focusLost(FocusEvent e) { + disposeOverlay(); + } + @Override + public void focusGained(FocusEvent e) {} + }); + + area.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true), disposeEditorString); + area.getActionMap().put(disposeEditorString, disposeEditor); + + area.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_DOWN_MASK, true), commitEditAndDisposeEditorString); + area.getActionMap().put(commitEditAndDisposeEditorString, commitEditAndDisposeEditor); + + area.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.ALT_DOWN_MASK, true), commitAndCreateNextDefaultNodeString); + area.getActionMap().put(commitAndCreateNextDefaultNodeString, commitAndCreateNextDefaultNode); + + area.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK, true), createContinueTalkingNodeString); + area.getActionMap().put(createContinueTalkingNodeString, createContinueTalkingNode); + + } + + + public void setSelected(WriterNode wrNode) { + prevSelected = selected; + selected = wrNode; + } + + public void selectAndScroll(WriterNode node) { + if (node != null) { + setSelected(node); + + m_vis.run("colors"); + m_vis.run("layout"); + m_vis.run("scrollToSelected"); + + revalidate(); + repaint(); + } + } + + public void selectScrollAndEdit(WriterNode node) { + if (node != null) { + setSelected(node); + + m_vis.run("colors"); + m_vis.run("layout"); + m_vis.run("scrollToSelectedAndEdit"); + + revalidate(); + repaint(); + } + } + + + + public WriterNode nextNodeRight(WriterNode wrNode) { + Node node = cells.get(wrNode); + Node nextNode = graph.getFirstTreeChild(node); + return nextNode == null ? null : (WriterNode) nextNode.get(TARGET); + } + + public WriterNode nextNodeLeft(WriterNode wrNode) { + Node node = cells.get(wrNode); + Node nextNode = graph.getTreeParent(node); + return nextNode == null ? null : (WriterNode) nextNode.get(TARGET); + } + + public WriterNode nextNodeUp(WriterNode wrNode) { + if (wrNode == null) return null; + Node node = cells.get(wrNode); + Node nextNode = graph.getPreviousTreeSibling(node); + if (nextNode == null) nextNode = findPreviousSiblingWithLastChildAtDepth(node, 0); + return nextNode == null ? null : (WriterNode) nextNode.get(TARGET); + } + + + private Node findPreviousSiblingWithLastChildAtDepth(Node node, int depth) { + if (graph.getTreeParent(node) == null) return null; + Node prevSibl = graph.getPreviousTreeSibling(node); + if (prevSibl != null) { + Node candidate = findLastChildAtDepth(prevSibl, depth); + return candidate != null ? candidate : findPreviousSiblingWithLastChildAtDepth(prevSibl, depth); + } else { + return findPreviousSiblingWithLastChildAtDepth(graph.getTreeParent(node), depth + 1); + } + } + + private Node findLastChildAtDepth(Node node, int depth) { + if (depth == 0) return node; + if (graph.getLastTreeChild(node) != null) return findLastChildAtDepth(graph.getLastTreeChild(node), depth - 1); + return findPreviousSiblingWithLastChildAtDepth(node, depth); + } + + + + public WriterNode nextNodeDown(WriterNode wrNode) { + if (wrNode == null) return null; + Node node = cells.get(wrNode); + Node nextNode = graph.getNextTreeSibling(node); + if (nextNode == null) nextNode = findNextSiblingWithLastChildAtDepth(node, 0); + return nextNode == null ? null : (WriterNode) nextNode.get(TARGET); + } + + + private Node findNextSiblingWithLastChildAtDepth(Node node, int depth) { + if (graph.getTreeParent(node) == null) return null; + Node nextSibl = graph.getNextTreeSibling(node); + if (nextSibl != null) { + Node candidate = findFirstChildAtDepth(nextSibl, depth); + return candidate != null ? candidate : findNextSiblingWithLastChildAtDepth(nextSibl, depth); + } else { + return findNextSiblingWithLastChildAtDepth(graph.getTreeParent(node), depth + 1); + } + } + + private Node findFirstChildAtDepth(Node node, int depth) { + if (depth == 0) return node; + if (graph.getFirstTreeChild(node) != null) return findFirstChildAtDepth(graph.getFirstTreeChild(node), depth - 1); + return findNextSiblingWithLastChildAtDepth(node, depth); + } + + + + + + public Point2D getScreenCoordinates(Point2D abs, Point2D screen) { + return getTransform().transform(abs, screen); + } + + + + + public class MyGraph extends Graph { + + + private String m_spanningTreeFilter; + private FilteredSpanningTree m_filteredSpanning = null; + + public MyGraph(boolean directed, String spanningFilterColumn) { + super(directed); + m_spanningTreeFilter = spanningFilterColumn; + } + + public Tree getFilteredSpanningTree() { + if ( m_filteredSpanning == null ) + return getFilteredSpanningTree((Node)nodes().next()); + else + return m_filteredSpanning; + } + + public Tree getFilteredSpanningTree(Node root) { + nodeCheck(root, true); + if ( m_filteredSpanning == null ) { + m_filteredSpanning = new FilteredSpanningTree(this, root, m_spanningTreeFilter); + } else if ( m_filteredSpanning.getRoot() != root ) { + m_filteredSpanning.buildSpanningTree(root); + } + return m_filteredSpanning; + } + + @Override + protected void updateDegrees(int e, int s, int t, int incr) { + super.updateDegrees(e, s, t, incr); + clearFilteredSpanningTree(); + } + + public void clearFilteredSpanningTree() { + m_filteredSpanning = null; + } + + public Node getTreeParent(Node n) { + return getFilteredSpanningTree().getParent(n); + } + + public Node getNextTreeSibling(Node n) { + return getFilteredSpanningTree().getNextSibling(n); + } + + public Node getPreviousTreeSibling(Node n) { + return getFilteredSpanningTree().getPreviousSibling(n); + } + + public Node getFirstTreeChild(Node n) { + return getFilteredSpanningTree().getFirstChild(n); + } + + public Node getLastTreeChild(Node n) { + return getFilteredSpanningTree().getLastChild(n); + } + } + +// class TooltipControl extends ControlAdapter { +// +// @Override +// public void itemEntered(VisualItem item, MouseEvent e) { +// if (item.get(TARGET) != null) { +// tooltippedItem = item; +// if (!tooltipActivated) { +// setToolTipText(""); +// ToolTipManager.sharedInstance().registerComponent(WriterGraphView.this); +// ToolTipManager.sharedInstance().setEnabled(true); +// tooltipActivated = true; +// } +// } +// } +// @Override +// public void itemExited(VisualItem item, MouseEvent e) { +// //Hides the tooltip... +// ToolTipManager.sharedInstance().setEnabled(false); +// ToolTipManager.sharedInstance().unregisterComponent(WriterGraphView.this); +// tooltipActivated = false; +// } +// } +// +// JToolTip tt = null; +// private VisualItem tooltippedItem = null; +// private VisualItem lastTTItem = null; +// private boolean tooltipActivated = false; +// +// @Override +// public Point getToolTipLocation(MouseEvent event) { +// return new Point(event.getX() + 5, event.getY() + 5); +// } +// +// @Override +// public JToolTip createToolTip() { +// if (tt == null) tt = super.createToolTip(); +// if (tooltippedItem == lastTTItem) { +// return tt; +// } +// tt = super.createToolTip(); +// lastTTItem = tooltippedItem; +// tt.setLayout(new BorderLayout()); +// JPanel content = new JPanel(); +// content.setLayout(new JideBoxLayout(content, JideBoxLayout.PAGE_AXIS)); +// JLabel label; +// if (tooltippedItem != null) { +// Object target = tooltippedItem.get(TARGET); +// if (target != null) { +// if (target instanceof Dialogue) { +// Dialogue d = (Dialogue) target; +// label = new JLabel(new ImageIcon(DefaultIcons.getDialogueIcon())); +// label.setText(d.id); +// content.add(label, JideBoxLayout.FIX); +// if (tooltippedItem.get(REPLY) == null) { +// if (d.rewards != null && !d.rewards.isEmpty()) { +// for (Dialogue.Reward r : d.rewards) { +// label = new JLabel(); +// DialogueEditor.decorateRewardJLabel(label, r); +// content.add(label, JideBoxLayout.FIX); +// } +// } +// } else { +// Object replObj = tooltippedItem.get(REPLY); +// if (replObj instanceof Dialogue.Reply) { +// Dialogue.Reply r = (Dialogue.Reply) replObj; +// if (r.requirements != null && !r.requirements.isEmpty()) { +// for (Requirement req : r.requirements) { +// label = new JLabel(); +// DialogueEditor.decorateRequirementJLabel(label, req); +// content.add(label, JideBoxLayout.FIX); +// } +// } +// } +// } +// } +// } +// +// } +// +// tt.add(content, BorderLayout.CENTER); +// tt.setPreferredSize(tt.getLayout().preferredLayoutSize(tt)); +// return tt; +// } + + } + +} diff --git a/src/prefuse/data/FilteredSpanningTree.java b/src/prefuse/data/FilteredSpanningTree.java new file mode 100644 index 0000000..c9db674 --- /dev/null +++ b/src/prefuse/data/FilteredSpanningTree.java @@ -0,0 +1,302 @@ +package prefuse.data; + +import java.util.BitSet; +import java.util.Iterator; +import java.util.LinkedList; + +import prefuse.data.Edge; +import prefuse.data.Graph; +import prefuse.data.Node; +import prefuse.data.Schema; +import prefuse.data.Table; +import prefuse.data.Tree; +import prefuse.data.Tuple; +import prefuse.data.tuple.TupleManager; +import prefuse.visual.tuple.TableEdgeItem; + +public class FilteredSpanningTree extends Tree { + + /** Extra edge table data field recording the id of the source edge + * a tree edge represents. */ + public static final String SOURCE_EDGE = "source"; + /** Edge table schema used by the spanning tree. */ + protected static final Schema EDGE_SCHEMA = new Schema(); + static { + EDGE_SCHEMA.addColumn(DEFAULT_SOURCE_KEY, int.class, new Integer(-1)); + EDGE_SCHEMA.addColumn(DEFAULT_TARGET_KEY, int.class, new Integer(-1)); + EDGE_SCHEMA.addColumn(SOURCE_EDGE, int.class); + } + + /** A reference to the backing graph that this tree spans. */ + protected Graph m_backing; + /** The boolean field to check. If false, edge is filtered out. */ + protected int m_filter; + + /** + * Create a new SpanningTree. + * @param g the backing Graph to span + * @param root the Node to use as the root of the spanning tree + * @param filterField the Edge column to use to filter out non-tree edges (must be boolean); + */ + public FilteredSpanningTree(Graph g, Node root, String filterField) { + super(g.getNodeTable(), EDGE_SCHEMA.instantiate()); + m_filter = g.getEdgeTable().getColumnNumber(filterField); + if (g.getEdgeTable().getColumn(m_filter) != null && !g.getEdgeTable().getColumn(m_filter).canGetBoolean()) { + throw new UnsupportedOperationException( + "The filter column must be boolean."); + } + m_backing = g; + TupleManager etm = new TupleManager(getEdgeTable(), null, + TableEdgeItem.class) { + public Tuple getTuple(int row) { + return m_backing.getEdge(m_table.getInt(row, SOURCE_EDGE)); + } + }; + getEdgeTable().setTupleManager(etm); + super.setTupleManagers(g.m_nodeTuples, etm); + buildSpanningTree(root); + } + + /** + * Build the spanning tree, starting at the given root. Uses an + * unweighted breadth first traversal to build the spanning tree. + * @param root the root node of the spanning tree + */ + public void buildSpanningTree(Node root) { + // re-use a previously allocated tree if possible + super.clearEdges(); + super.setRoot(root); + + // build unweighted spanning tree by BFS + LinkedList q = new LinkedList(); + BitSet visit = new BitSet(); + q.add(root); visit.set(root.getRow()); + Table edges = getEdgeTable(); + + while ( !q.isEmpty() ) { + Node p = (Node)q.removeFirst(); + for ( Iterator iter = p.edges(); iter.hasNext(); ) { + Edge e = (Edge)iter.next(); + if (e.getBoolean(m_filter)) { + Node n = e.getAdjacentNode(p); + if ( !visit.get(n.getRow()) ) { + q.add(n); visit.set(n.getRow()); + int er = super.addChildEdge(p.getRow(), n.getRow()); + edges.setInt(er, SOURCE_EDGE, e.getRow()); + } + } + } + } + } + + // ------------------------------------------------------------------------ + // Disallow most mutator methods + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#addChild(int) + */ + public int addChild(int parent) { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#addChild(prefuse.data.Node) + */ + public Node addChild(Node parent) { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#addChildEdge(int, int) + */ + public int addChildEdge(int parent, int child) { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#addChildEdge(prefuse.data.Node, prefuse.data.Node) + */ + public Edge addChildEdge(Node parent, Node child) { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#addRoot() + */ + public Node addRoot() { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#addRootRow() + */ + public int addRootRow() { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#removeChild(int) + */ + public boolean removeChild(int node) { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#removeChild(prefuse.data.Node) + */ + public boolean removeChild(Node n) { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#removeChildEdge(prefuse.data.Edge) + */ + public boolean removeChildEdge(Edge e) { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#removeChildEdge(int) + */ + public boolean removeChildEdge(int edge) { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Tree#setRoot(prefuse.data.Node) + */ + void setRoot(Node root) { + throw new UnsupportedOperationException( + "Changes to tree structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Graph#addEdge(int, int) + */ + public int addEdge(int s, int t) { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Graph#addEdge(prefuse.data.Node, prefuse.data.Node) + */ + public Edge addEdge(Node s, Node t) { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Graph#addNode() + */ + public Node addNode() { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Graph#addNodeRow() + */ + public int addNodeRow() { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.tuple.TupleSet#clear() + */ + public void clear() { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Graph#removeEdge(prefuse.data.Edge) + */ + public boolean removeEdge(Edge e) { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Graph#removeEdge(int) + */ + public boolean removeEdge(int edge) { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Graph#removeNode(int) + */ + public boolean removeNode(int node) { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Graph#removeNode(prefuse.data.Node) + */ + public boolean removeNode(Node n) { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.tuple.TupleSet#removeTuple(prefuse.data.Tuple) + */ + public boolean removeTuple(Tuple t) { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Graph#setEdgeTable(prefuse.data.Table) + */ + public void setEdgeTable(Table edges) { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + + /** + * Unsupported operation. Spanning trees should not be edited. + * @see prefuse.data.Graph#setTupleManagers(prefuse.data.tuple.TupleManager, prefuse.data.tuple.TupleManager) + */ + public void setTupleManagers(TupleManager ntm, TupleManager etm) { + throw new UnsupportedOperationException( + "Changes to graph structure not allowed for spanning trees."); + } + +}