import processing.core.*; import processing.data.*; import processing.event.*; import processing.opengl.*; import java.util.HashMap; import java.util.ArrayList; import java.io.File; import java.io.BufferedReader; import java.io.PrintWriter; import java.io.InputStream; import java.io.OutputStream; import java.io.IOException; public class ontology extends PApplet { Node nodes[] = new Node[1000]; Link links[] = new Link[6000]; int textSize = 14; boolean darkMode = true; public void setup() { surface.setResizable(true); textSize(textSize); initButtons(); /** * When saving, a copy of the save-file is stored locally which is opened when starting the program */ try { loadCSV(dataPath("save.csv")); } catch(Exception e) { println("No File found, starting blank"); } } public void draw() { background(darkMode? 0 : 255); /** * Update and display links */ for (int i = 0; i < linkCount; i++) { if (links[i].active) { links[i].update(); links[i].display(); } } /** * Update and display nodes */ for (int i = 0; i < nodeCount; i++) { if (!nodes[i].deleted) { nodes[i].update(); nodes[i].display(); } } /** * Update and display ui, hide via exporting-flag for easy image-saving * Not all buttons always show, that's why this isn't in a loop */ if (!exporting) { textSize(14); rectMode(CORNER); textAlign(CORNER); buttons[0].display(); if (i_selectedNode != -1) buttons[1].display(); buttons[2].display(); buttons[3].display(); buttons[4].display(); buttons[5].display(); rectMode(CENTER); textAlign(CENTER); textSize(textSize); } else { save(savePNGpath); exporting = false; } /** * Pull "wire" out of in/outlet before creating a link (see mouseReleased in ui) */ if (dragLink) { stroke(darkMode? 255:0); strokeWeight(2); if (dragOriginLet == -1) { line(nodes[dragOriginId].inletCenter.x, nodes[dragOriginId].inletCenter.y, mouseX, mouseY); } else if (dragOriginLet == 1) { line(nodes[dragOriginId].outletCenter.x, nodes[dragOriginId].outletCenter.y, mouseX, mouseY); } } } int buttonCount = 6; int i_buttonId; public void initButtons() { buttons[0] = new Button(10, 10, "Add Node"); buttons[1] = new Button(104, 10, "Delete Node"); buttons[2] = new Button(10, 50, "Dark/Bright Mode"); buttons[3] = new Button(10, 90, "Save"); buttons[4] = new Button(10, 130, "Open"); buttons[5] = new Button(10, 170, "Export Image"); /* buttons[0] = new Button(10, 10, "Node Hinzufügen"); buttons[1] = new Button(155, 10, "Löschen"); buttons[2] = new Button(10, 50, "Farben umkehren"); buttons[3] = new Button(10, 90, "Speichern"); buttons[4] = new Button(10, 130, "Öffnen"); buttons[5] = new Button(10, 170, "Bild Exportieren");*/ } public void buttonFunctions(int functionID) { switch (functionID) { case(0): addNode(PApplet.parseInt(random(50, width-50)), PApplet.parseInt(random(30, height-150)), ""); i_selectedNode = nodeCount-1; break; case(1): deleteSelectedNode(); break; case(2): darkMode = !darkMode; break; case(3): saveCSV(dataPath("save.csv")); selectOutput("Where to save .csv file to?", "saveCSVFile"); break; case(4): selectInput("Select csv File", "loadCSVFile"); break; case(5): selectOutput("Where to export .png image file to?", "savePNGFile"); break; default: break; } } Button buttons[] = new Button[buttonCount]; class Button { int id, x, y, w, h; String label; public boolean hover() { return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; } public void click() { buttonFunctions(id); } Button(int x_, int y_, String label_) { id = i_buttonId; i_buttonId++; x = x_; y = y_ ; label = label_; w = PApplet.parseInt(textWidth(label))+18; h = 32; } public void display() { stroke(0); strokeWeight(1); fill(255, 127); rect(x, y, w, h, 4); fill(0); text(label, x+9, y+h/2+5); } } int linkCount; boolean dragLink; //state of dragging a link int dragOriginLet = 0; //dragging from inlet: -1. dragging from outlet: 1. int dragOriginId = -1; //which nodes out- / inlet did user click on? public void Link(int parentNode, int targetNode) { /** * Check if user is adding or removing a link */ int exists = -1; for (int i = 0; i < linkCount; i++) { if (links[i].parentNode == parentNode && links[i].targetNode == targetNode) { exists = i; break; } } if (exists != -1) { /** * !active state is pretty much the same as deleted state that the node-class comes with */ links[exists].active = !links[exists].active; } else { links[linkCount] = new Link(parentNode, targetNode); linkCount++; } } class Link { int parentNode, targetNode; boolean active; PVector link = new PVector(0, 0); Link(int parentNode_, int targetNode_) { parentNode = parentNode_; targetNode = targetNode_; active = true; } /** * This could be an event that only gets called when nodes are moved or resized. */ public void update() { link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; } public void display() { stroke(darkMode? 255 : 0); strokeWeight(2); pushMatrix(); translate(nodes[parentNode].outletCenter.x, nodes[parentNode].outletCenter.y); line(0, 0, link.x, link.y); translate(-nodes[parentNode].outletCenter.x+(nodes[parentNode].outletCenter.x+nodes[targetNode].inletCenter.x)/2, -nodes[parentNode].outletCenter.y+(nodes[parentNode].outletCenter.y+nodes[targetNode].inletCenter.y)/2); rotate(link.heading()); fill(darkMode? 0 : 255); triangle(0, textSize/2, textSize, 0, 0, -textSize/2); popMatrix(); } } int nodeCount; int i_selectedNode = -1; int padding = textSize/3*2; //Padding between text and surrounding box int letTolerance = 10; //Widens the clickable areas of in/outlets a bit public void addNode(int x, int y, String title) { nodes[nodeCount] = new Node(x, y, title); nodeCount++; } public void deselect() { i_selectedNode = -1; } public void deleteSelectedNode() { //Seperate from class for garbage collector reasons if (i_selectedNode != -1) { for (int i = 0; i < linkCount; i++) { if (links[i].targetNode == i_selectedNode || links[i].parentNode == i_selectedNode) { links[i].active = false; } } nodes[i_selectedNode].deleted = true; i_selectedNode = -1; } } class Node { String title; int id; int inlets = 2; int outlets = 2; int x, y; int h = textSize+2*padding; int w = textSize+2*padding; PVector dragMouseStartPoint = new PVector(0, 0); PVector outletCenter = new PVector(0, 0); PVector inletCenter = new PVector(0, 0); boolean deleted; boolean drag; public boolean hover() { if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { return true; } else { return false; } } public boolean hoverOutlet() { if (mouseX > x-(textSize+padding)/2-letTolerance && mouseX < x+(textSize+padding)/2+letTolerance && mouseY > y-padding-textSize/2-(textSize+padding)/2-letTolerance && mouseY < y-padding-textSize/2) { return true; } else { return false; } } public boolean hoverInlet() { if (mouseX > x-(textSize+padding)/2-letTolerance && mouseX < x+(textSize+padding)/2+letTolerance && mouseY > y+padding+textSize/2 && mouseY < y+padding+textSize/2+(textSize+padding)/2+letTolerance) { return true; } else { return false; } } Node(int x_, int y_, String title_) { id = nodeCount; title = title_; x = x_; y = y_; setVectors(); refreshSize(); } public void setVectors() { outletCenter.x = x; outletCenter.y = y-(padding+textSize/2)*1.3f; inletCenter.x = x; inletCenter.y = y+(padding+textSize/2)*1.3f; } /** * Called when changing nodes title or when zooming via mouse-wheel */ public void refreshSize() { w = PApplet.parseInt(textWidth(title))+2*padding; h = textSize+2*padding; } /** * Update-function is really only necessary when dragging the node */ public void update() { if (drag) { x -= dragMouseStartPoint.x - mouseX; dragMouseStartPoint.x = mouseX; y -= dragMouseStartPoint.y - mouseY; dragMouseStartPoint.y = mouseY; setVectors(); } } public void display() { /** * Selected nodes border */ if (i_selectedNode == id) { noStroke(); fill(150, 100); rect(x, y, w+20, h+20, 4); } /** * Inlet & Outlet */ stroke(255, darkMode? 255 : 0); strokeWeight(1); fill(255, 0, 0); ellipse(x, y-padding-textSize/2, textSize+padding, textSize+padding); fill(0); ellipse(x, y+padding+textSize/2, textSize+padding, textSize+padding); /** * Box & Title */ stroke(127); strokeWeight(2); fill(50); rect(x, y, w, h, 4); fill(255); text(title, x, y+textSize/3); } } /** * Saving and loading is a messy hack! * You'll get a blank line in your save file for each deleted object. * That's because of my lack of skills of dodging the garbage collector. * I didn't loose data to corruption yet. * Don't touch the save files if you don't want to mess them up! **/ boolean exporting; String savePNGpath = dataPath("save.png"); public void savePNGFile(File selection) { savePNGpath = selection.getPath(); exporting = true; } public void saveCSVFile(File selection) { saveCSV(selection.getPath()); } public void loadCSVFile(File selection) { loadCSV(selection.getPath()); } public void loadCSV(String path) { for (int i = 0; i < nodeCount; i++) { //nodes[i].deleted = true; i_selectedNode = i; deleteSelectedNode(); } nodeCount = 0; for (int i = 0; i < linkCount; i++) { links[i].active = false; } linkCount = 0; String[] loadString = loadStrings(path); for (int i = 0; i < loadString.length-1; i++) { //This is ultra shitty if (loadString[i].length() == 0) { addNode(-1000, -1000, " "); i_selectedNode = nodeCount - 1; deleteSelectedNode(); //nodes[nodeCount-1].deleted = true; } else { String nodeData[] = split(loadString[i], ','); addNode(PApplet.parseInt(nodeData[1]), PApplet.parseInt(nodeData[2]), nodeData[0]); } } for (int i = 0; i < loadString.length-1; i++) { String nodeData[] = split(loadString[i], ','); for (int j = 3; j < nodeData.length; j++) { Link(i, PApplet.parseInt(nodeData[j])); } } } public void saveCSV(String path) { String saveString = ""; for (int i = 0; i < nodeCount; i++) { if (!nodes[i].deleted) { saveString += nodes[i].title + "," + nodes[i].x + "," + nodes[i].y; for (int j = 0; j < linkCount; j++) { if (links[j].parentNode == i && links[j].active) { saveString += "," + str(links[j].targetNode); } } } saveString += "\n"; } String[] saveArray = split(saveString, '\n'); saveStrings(path, saveArray); } /** * Keys only get caught for changing a nodes title. * Previous title is remembered in savedTitle and restored when hitting Escape */ String savedTitle; public void keyPressed() { if (key == ESC) { key = 0; } if (i_selectedNode != -1) { if (keyCode == BACKSPACE) { if (nodes[i_selectedNode].title.length() > 0) { nodes[i_selectedNode].title = nodes[i_selectedNode].title.substring(0, nodes[i_selectedNode].title.length()-1); nodes[i_selectedNode].refreshSize(); } } else if (keyCode == DELETE) { deleteSelectedNode(); } else if (keyCode == ESC) { nodes[i_selectedNode].title = savedTitle; nodes[i_selectedNode].refreshSize(); deselect(); } else if (keyCode == RETURN || keyCode == ENTER) { deselect(); } else if (keyCode != SHIFT && keyCode != RETURN && keyCode != ENTER && keyCode != CONTROL) { nodes[i_selectedNode].title = nodes[i_selectedNode].title + key; nodes[i_selectedNode].refreshSize(); } } } public void mousePressed() { if (mouseButton == LEFT) { /** * Either press a button... */ boolean buttonClicked = false; for (int i = 0; i < buttons.length; i++) { if (buttons[i].hover()) { buttons[i].click(); //Not able to break out of here without using a flag buttonClicked = true; } } if (!buttonClicked) { for (int i = 0; i < nodeCount; i++) { /** * ...or select / drag a node... */ if (nodes[i].hover()) { i_selectedNode = i; savedTitle = nodes[i].title; nodes[i].drag = true; nodes[i].dragMouseStartPoint.x = mouseX; nodes[i].dragMouseStartPoint.y = mouseY; break; } /** * ...or pull a new link out of an inlet or outlet. */ else if (nodes[i].hoverInlet()) { dragLink = true; dragOriginLet = -1; dragOriginId = i; } else if (nodes[i].hoverOutlet()) { dragLink = true; dragOriginLet = 1; dragOriginId = i; } //deselecting the shit out of this for reasons I don't remember, no care to improve right now deselect(); } } } } /** * Create links */ public void mouseReleased() { for (int i = 0; i < nodeCount; i++) { nodes[i].drag = false; if (dragLink && dragOriginId != i) { if (nodes[i].hoverInlet() && dragOriginLet == 1) { Link(dragOriginId, i); } if (nodes[i].hoverOutlet() && dragOriginLet == -1) { Link(i, dragOriginId); } } } /** * Reset drag&drop states */ dragLink = false; dragOriginLet = 0; dragOriginId = -1; } /** * Scaling is tied to textSize. Having the UI on a seperate PGraphics would be better, but it's good enough for now */ int zoomDelta = 3; public void mouseWheel(MouseEvent event) { float zoom = event.getAmount(); if (zoom < 0) { textSize += zoomDelta; } else if (zoom > 0) { if (textSize - zoomDelta > 0) { textSize -= zoomDelta; } } padding = textSize/3*2; for (int i = 0; i < nodeCount; i++) { nodes[i].refreshSize(); nodes[i].setVectors(); } } public void settings() { size(800, 600); } static public void main(String[] passedArgs) { String[] appletArgs = new String[] { "ontology" }; if (passedArgs != null) { PApplet.main(concat(appletArgs, passedArgs)); } else { PApplet.main(appletArgs); } } }