commit f591210c2e37c2ed83ad3a699ec7f44e0476fda1 Author: Victor Giers Date: Mon May 6 03:24:40 2019 +0200 initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..da08f46 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Simple Ontology +## Define some truth ! +**Create custom node network graphs aka ontologies.** +Tool to make ontological network topologies with. +Has saving and reopening functionality and exports images. +Smallest alternative to protégé, ONTOLIS or OntoStudio. + +**How to use** +Add nodes by clicking on "Add Node". +Select a node by clicking on it and type on your keyboard to give it a title / rename it. +Link nodes by dragging "wires" out of the inlets / outlets and plug them into another nodes inlets / outlets (inspired from Blenders node based shading and compositing system). +You can only connect black with red / red with black. Black is inlet, red is outlet. You can not connect inlet with inlet or outlet with outlet. + +You need Java 1.8 to run the builds. + + +**Ideas** +* Subtitles and image-import for each node +* Seperately render UI from workspace +* Database +* Make workspace dragable / movable (like Google maps) +* draw only when necessary (more events) (ditch Processing?) +* Test if it runs on windows +* Even more testing! + +Pretty sure I'm gonna leave it as is for the time being though. diff --git a/build/application.linux-arm64/data/save.csv b/build/application.linux-arm64/data/save.csv new file mode 100644 index 0000000..4ed18e7 --- /dev/null +++ b/build/application.linux-arm64/data/save.csv @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Biological Process,395,537,32,33 +Physiological Process,292,455,34 +Cellular Process,483,455,34 +Cellular Physiological Process,391,375,36,35 +Cell Cycle,305,296,40,41 +Cell Division,544,265,37 +Cytokinesis,543,165,38 +Cytokinesis after Meiosis,399,54 +M Phase of Meiotic Cell Cycle,310,144,38 +M Phase,239,225,39 +Meiotic Cell Cycle,392,225,39 + diff --git a/build/application.linux-arm64/lib/core.jar b/build/application.linux-arm64/lib/core.jar new file mode 100644 index 0000000..efabaad Binary files /dev/null and b/build/application.linux-arm64/lib/core.jar differ diff --git a/build/application.linux-arm64/lib/gluegen-rt-natives-linux-aarch64.jar b/build/application.linux-arm64/lib/gluegen-rt-natives-linux-aarch64.jar new file mode 100644 index 0000000..94c6d5e Binary files /dev/null and b/build/application.linux-arm64/lib/gluegen-rt-natives-linux-aarch64.jar differ diff --git a/build/application.linux-arm64/lib/gluegen-rt.jar b/build/application.linux-arm64/lib/gluegen-rt.jar new file mode 100644 index 0000000..742fdb2 Binary files /dev/null and b/build/application.linux-arm64/lib/gluegen-rt.jar differ diff --git a/build/application.linux-arm64/lib/jogl-all-natives-linux-aarch64.jar b/build/application.linux-arm64/lib/jogl-all-natives-linux-aarch64.jar new file mode 100644 index 0000000..c856f34 Binary files /dev/null and b/build/application.linux-arm64/lib/jogl-all-natives-linux-aarch64.jar differ diff --git a/build/application.linux-arm64/lib/jogl-all.jar b/build/application.linux-arm64/lib/jogl-all.jar new file mode 100644 index 0000000..8925b00 Binary files /dev/null and b/build/application.linux-arm64/lib/jogl-all.jar differ diff --git a/build/application.linux-arm64/lib/ontology.jar b/build/application.linux-arm64/lib/ontology.jar new file mode 100644 index 0000000..8379e9f Binary files /dev/null and b/build/application.linux-arm64/lib/ontology.jar differ diff --git a/build/application.linux-arm64/ontology b/build/application.linux-arm64/ontology new file mode 100755 index 0000000..08dbd27 --- /dev/null +++ b/build/application.linux-arm64/ontology @@ -0,0 +1,5 @@ +#!/bin/sh + +APPDIR=$(readlink -f "$0") +APPDIR=$(dirname "$APPDIR") +java -Xms64m -Xmx12000m -Djna.nosys=true -Djava.library.path="$APPDIR:$APPDIR/lib" -cp "$APPDIR:$APPDIR/lib/ontology.jar:$APPDIR/lib/core.jar:$APPDIR/lib/jogl-all.jar:$APPDIR/lib/gluegen-rt.jar:$APPDIR/lib/jogl-all-natives-linux-aarch64.jar:$APPDIR/lib/gluegen-rt-natives-linux-aarch64.jar" ontology "$@" diff --git a/build/application.linux-arm64/source/buttons.pde b/build/application.linux-arm64/source/buttons.pde new file mode 100644 index 0000000..525ddb1 --- /dev/null +++ b/build/application.linux-arm64/source/buttons.pde @@ -0,0 +1,78 @@ +int buttonCount = 6; +int i_buttonId; + +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");*/ +} + +void buttonFunctions(int functionID) { + switch (functionID) { + case(0): + addNode(int(random(50, width-50)), int(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; + + boolean hover() { + return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; + } + + void click() { + buttonFunctions(id); + } + Button(int x_, int y_, String label_) { + id = i_buttonId; + i_buttonId++; + x = x_; + y = y_ ; + label = label_; + w = int(textWidth(label))+18; + h = 32; + } + + 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); + } +} diff --git a/build/application.linux-arm64/source/links.pde b/build/application.linux-arm64/source/links.pde new file mode 100644 index 0000000..831ad75 --- /dev/null +++ b/build/application.linux-arm64/source/links.pde @@ -0,0 +1,60 @@ +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? + +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. + */ + void update() { + link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; + link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; + } + + 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(); + } +} diff --git a/build/application.linux-arm64/source/nodes.pde b/build/application.linux-arm64/source/nodes.pde new file mode 100644 index 0000000..0f1300d --- /dev/null +++ b/build/application.linux-arm64/source/nodes.pde @@ -0,0 +1,129 @@ +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 + +void addNode(int x, int y, String title) { + nodes[nodeCount] = new Node(x, y, title); + nodeCount++; +} + +void deselect() { + i_selectedNode = -1; +} + +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; + boolean hover() { + if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { + return true; + } else { + return false; + } + } + 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; + } + } + 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(); + } + + void setVectors() { + outletCenter.x = x; + outletCenter.y = y-(padding+textSize/2)*1.3; + inletCenter.x = x; + inletCenter.y = y+(padding+textSize/2)*1.3; + } + + /** + * Called when changing nodes title or when zooming via mouse-wheel + */ + void refreshSize() { + w = int(textWidth(title))+2*padding; + h = textSize+2*padding; + } + + /** + * Update-function is really only necessary when dragging the node + */ + void update() { + if (drag) { + x -= dragMouseStartPoint.x - mouseX; + dragMouseStartPoint.x = mouseX; + y -= dragMouseStartPoint.y - mouseY; + dragMouseStartPoint.y = mouseY; + setVectors(); + } + } + + 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); + } +} diff --git a/build/application.linux-arm64/source/ontology.java b/build/application.linux-arm64/source/ontology.java new file mode 100644 index 0000000..f7bfd3a --- /dev/null +++ b/build/application.linux-arm64/source/ontology.java @@ -0,0 +1,577 @@ +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); + } + } +} diff --git a/build/application.linux-arm64/source/ontology.pde b/build/application.linux-arm64/source/ontology.pde new file mode 100644 index 0000000..07edee4 --- /dev/null +++ b/build/application.linux-arm64/source/ontology.pde @@ -0,0 +1,83 @@ +Node nodes[] = new Node[1000]; +Link links[] = new Link[6000]; + +int textSize = 14; +boolean darkMode = true; + +void setup() { + size(800, 600); + 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"); + } +} + +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); + } + } +} diff --git a/build/application.linux-arm64/source/save_load.pde b/build/application.linux-arm64/source/save_load.pde new file mode 100644 index 0000000..eb7501b --- /dev/null +++ b/build/application.linux-arm64/source/save_load.pde @@ -0,0 +1,74 @@ +/** + * 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"); + +void savePNGFile(File selection) { + savePNGpath = selection.getPath(); + exporting = true; +} + +void saveCSVFile(File selection) { + saveCSV(selection.getPath()); +} + +void loadCSVFile(File selection) { + loadCSV(selection.getPath()); +} + +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(int(nodeData[1]), int(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, int(nodeData[j])); + } + } +} + +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); +} diff --git a/build/application.linux-arm64/source/ui.pde b/build/application.linux-arm64/source/ui.pde new file mode 100644 index 0000000..67f9b3a --- /dev/null +++ b/build/application.linux-arm64/source/ui.pde @@ -0,0 +1,124 @@ +/** + * Keys only get caught for changing a nodes title. + * Previous title is remembered in savedTitle and restored when hitting Escape + */ +String savedTitle; +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(); + } + } +} + + +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 + */ +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; +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(); + } +} diff --git a/build/application.linux-armv6hf/data/save.csv b/build/application.linux-armv6hf/data/save.csv new file mode 100644 index 0000000..4ed18e7 --- /dev/null +++ b/build/application.linux-armv6hf/data/save.csv @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Biological Process,395,537,32,33 +Physiological Process,292,455,34 +Cellular Process,483,455,34 +Cellular Physiological Process,391,375,36,35 +Cell Cycle,305,296,40,41 +Cell Division,544,265,37 +Cytokinesis,543,165,38 +Cytokinesis after Meiosis,399,54 +M Phase of Meiotic Cell Cycle,310,144,38 +M Phase,239,225,39 +Meiotic Cell Cycle,392,225,39 + diff --git a/build/application.linux-armv6hf/lib/core.jar b/build/application.linux-armv6hf/lib/core.jar new file mode 100644 index 0000000..efabaad Binary files /dev/null and b/build/application.linux-armv6hf/lib/core.jar differ diff --git a/build/application.linux-armv6hf/lib/gluegen-rt-natives-linux-armv6hf.jar b/build/application.linux-armv6hf/lib/gluegen-rt-natives-linux-armv6hf.jar new file mode 100644 index 0000000..5ef0673 Binary files /dev/null and b/build/application.linux-armv6hf/lib/gluegen-rt-natives-linux-armv6hf.jar differ diff --git a/build/application.linux-armv6hf/lib/gluegen-rt.jar b/build/application.linux-armv6hf/lib/gluegen-rt.jar new file mode 100644 index 0000000..742fdb2 Binary files /dev/null and b/build/application.linux-armv6hf/lib/gluegen-rt.jar differ diff --git a/build/application.linux-armv6hf/lib/jogl-all-natives-linux-armv6hf.jar b/build/application.linux-armv6hf/lib/jogl-all-natives-linux-armv6hf.jar new file mode 100644 index 0000000..5aea1b2 Binary files /dev/null and b/build/application.linux-armv6hf/lib/jogl-all-natives-linux-armv6hf.jar differ diff --git a/build/application.linux-armv6hf/lib/jogl-all.jar b/build/application.linux-armv6hf/lib/jogl-all.jar new file mode 100644 index 0000000..8925b00 Binary files /dev/null and b/build/application.linux-armv6hf/lib/jogl-all.jar differ diff --git a/build/application.linux-armv6hf/lib/ontology.jar b/build/application.linux-armv6hf/lib/ontology.jar new file mode 100644 index 0000000..8379e9f Binary files /dev/null and b/build/application.linux-armv6hf/lib/ontology.jar differ diff --git a/build/application.linux-armv6hf/ontology b/build/application.linux-armv6hf/ontology new file mode 100755 index 0000000..c195fcc --- /dev/null +++ b/build/application.linux-armv6hf/ontology @@ -0,0 +1,5 @@ +#!/bin/sh + +APPDIR=$(readlink -f "$0") +APPDIR=$(dirname "$APPDIR") +java -Djna.nosys=true -Djava.library.path="$APPDIR:$APPDIR/lib" -cp "$APPDIR:$APPDIR/lib/ontology.jar:$APPDIR/lib/core.jar:$APPDIR/lib/jogl-all.jar:$APPDIR/lib/gluegen-rt.jar:$APPDIR/lib/jogl-all-natives-linux-armv6hf.jar:$APPDIR/lib/gluegen-rt-natives-linux-armv6hf.jar" ontology "$@" diff --git a/build/application.linux-armv6hf/source/buttons.pde b/build/application.linux-armv6hf/source/buttons.pde new file mode 100644 index 0000000..525ddb1 --- /dev/null +++ b/build/application.linux-armv6hf/source/buttons.pde @@ -0,0 +1,78 @@ +int buttonCount = 6; +int i_buttonId; + +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");*/ +} + +void buttonFunctions(int functionID) { + switch (functionID) { + case(0): + addNode(int(random(50, width-50)), int(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; + + boolean hover() { + return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; + } + + void click() { + buttonFunctions(id); + } + Button(int x_, int y_, String label_) { + id = i_buttonId; + i_buttonId++; + x = x_; + y = y_ ; + label = label_; + w = int(textWidth(label))+18; + h = 32; + } + + 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); + } +} diff --git a/build/application.linux-armv6hf/source/links.pde b/build/application.linux-armv6hf/source/links.pde new file mode 100644 index 0000000..831ad75 --- /dev/null +++ b/build/application.linux-armv6hf/source/links.pde @@ -0,0 +1,60 @@ +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? + +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. + */ + void update() { + link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; + link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; + } + + 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(); + } +} diff --git a/build/application.linux-armv6hf/source/nodes.pde b/build/application.linux-armv6hf/source/nodes.pde new file mode 100644 index 0000000..0f1300d --- /dev/null +++ b/build/application.linux-armv6hf/source/nodes.pde @@ -0,0 +1,129 @@ +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 + +void addNode(int x, int y, String title) { + nodes[nodeCount] = new Node(x, y, title); + nodeCount++; +} + +void deselect() { + i_selectedNode = -1; +} + +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; + boolean hover() { + if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { + return true; + } else { + return false; + } + } + 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; + } + } + 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(); + } + + void setVectors() { + outletCenter.x = x; + outletCenter.y = y-(padding+textSize/2)*1.3; + inletCenter.x = x; + inletCenter.y = y+(padding+textSize/2)*1.3; + } + + /** + * Called when changing nodes title or when zooming via mouse-wheel + */ + void refreshSize() { + w = int(textWidth(title))+2*padding; + h = textSize+2*padding; + } + + /** + * Update-function is really only necessary when dragging the node + */ + void update() { + if (drag) { + x -= dragMouseStartPoint.x - mouseX; + dragMouseStartPoint.x = mouseX; + y -= dragMouseStartPoint.y - mouseY; + dragMouseStartPoint.y = mouseY; + setVectors(); + } + } + + 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); + } +} diff --git a/build/application.linux-armv6hf/source/ontology.java b/build/application.linux-armv6hf/source/ontology.java new file mode 100644 index 0000000..f7bfd3a --- /dev/null +++ b/build/application.linux-armv6hf/source/ontology.java @@ -0,0 +1,577 @@ +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); + } + } +} diff --git a/build/application.linux-armv6hf/source/ontology.pde b/build/application.linux-armv6hf/source/ontology.pde new file mode 100644 index 0000000..07edee4 --- /dev/null +++ b/build/application.linux-armv6hf/source/ontology.pde @@ -0,0 +1,83 @@ +Node nodes[] = new Node[1000]; +Link links[] = new Link[6000]; + +int textSize = 14; +boolean darkMode = true; + +void setup() { + size(800, 600); + 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"); + } +} + +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); + } + } +} diff --git a/build/application.linux-armv6hf/source/save_load.pde b/build/application.linux-armv6hf/source/save_load.pde new file mode 100644 index 0000000..eb7501b --- /dev/null +++ b/build/application.linux-armv6hf/source/save_load.pde @@ -0,0 +1,74 @@ +/** + * 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"); + +void savePNGFile(File selection) { + savePNGpath = selection.getPath(); + exporting = true; +} + +void saveCSVFile(File selection) { + saveCSV(selection.getPath()); +} + +void loadCSVFile(File selection) { + loadCSV(selection.getPath()); +} + +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(int(nodeData[1]), int(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, int(nodeData[j])); + } + } +} + +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); +} diff --git a/build/application.linux-armv6hf/source/ui.pde b/build/application.linux-armv6hf/source/ui.pde new file mode 100644 index 0000000..67f9b3a --- /dev/null +++ b/build/application.linux-armv6hf/source/ui.pde @@ -0,0 +1,124 @@ +/** + * Keys only get caught for changing a nodes title. + * Previous title is remembered in savedTitle and restored when hitting Escape + */ +String savedTitle; +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(); + } + } +} + + +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 + */ +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; +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(); + } +} diff --git a/build/application.linux32/data/save.csv b/build/application.linux32/data/save.csv new file mode 100644 index 0000000..4ed18e7 --- /dev/null +++ b/build/application.linux32/data/save.csv @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Biological Process,395,537,32,33 +Physiological Process,292,455,34 +Cellular Process,483,455,34 +Cellular Physiological Process,391,375,36,35 +Cell Cycle,305,296,40,41 +Cell Division,544,265,37 +Cytokinesis,543,165,38 +Cytokinesis after Meiosis,399,54 +M Phase of Meiotic Cell Cycle,310,144,38 +M Phase,239,225,39 +Meiotic Cell Cycle,392,225,39 + diff --git a/build/application.linux32/lib/core.jar b/build/application.linux32/lib/core.jar new file mode 100644 index 0000000..efabaad Binary files /dev/null and b/build/application.linux32/lib/core.jar differ diff --git a/build/application.linux32/lib/gluegen-rt-natives-linux-i586.jar b/build/application.linux32/lib/gluegen-rt-natives-linux-i586.jar new file mode 100644 index 0000000..914a259 Binary files /dev/null and b/build/application.linux32/lib/gluegen-rt-natives-linux-i586.jar differ diff --git a/build/application.linux32/lib/gluegen-rt.jar b/build/application.linux32/lib/gluegen-rt.jar new file mode 100644 index 0000000..742fdb2 Binary files /dev/null and b/build/application.linux32/lib/gluegen-rt.jar differ diff --git a/build/application.linux32/lib/jogl-all-natives-linux-i586.jar b/build/application.linux32/lib/jogl-all-natives-linux-i586.jar new file mode 100644 index 0000000..88a27ce Binary files /dev/null and b/build/application.linux32/lib/jogl-all-natives-linux-i586.jar differ diff --git a/build/application.linux32/lib/jogl-all.jar b/build/application.linux32/lib/jogl-all.jar new file mode 100644 index 0000000..8925b00 Binary files /dev/null and b/build/application.linux32/lib/jogl-all.jar differ diff --git a/build/application.linux32/lib/ontology.jar b/build/application.linux32/lib/ontology.jar new file mode 100644 index 0000000..8379e9f Binary files /dev/null and b/build/application.linux32/lib/ontology.jar differ diff --git a/build/application.linux32/ontology b/build/application.linux32/ontology new file mode 100755 index 0000000..2e02cb5 --- /dev/null +++ b/build/application.linux32/ontology @@ -0,0 +1,5 @@ +#!/bin/sh + +APPDIR=$(readlink -f "$0") +APPDIR=$(dirname "$APPDIR") +java -Xms64m -Xmx12000m -Djna.nosys=true -Djava.library.path="$APPDIR:$APPDIR/lib" -cp "$APPDIR:$APPDIR/lib/ontology.jar:$APPDIR/lib/core.jar:$APPDIR/lib/jogl-all.jar:$APPDIR/lib/gluegen-rt.jar:$APPDIR/lib/jogl-all-natives-linux-i586.jar:$APPDIR/lib/gluegen-rt-natives-linux-i586.jar" ontology "$@" diff --git a/build/application.linux32/source/buttons.pde b/build/application.linux32/source/buttons.pde new file mode 100644 index 0000000..525ddb1 --- /dev/null +++ b/build/application.linux32/source/buttons.pde @@ -0,0 +1,78 @@ +int buttonCount = 6; +int i_buttonId; + +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");*/ +} + +void buttonFunctions(int functionID) { + switch (functionID) { + case(0): + addNode(int(random(50, width-50)), int(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; + + boolean hover() { + return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; + } + + void click() { + buttonFunctions(id); + } + Button(int x_, int y_, String label_) { + id = i_buttonId; + i_buttonId++; + x = x_; + y = y_ ; + label = label_; + w = int(textWidth(label))+18; + h = 32; + } + + 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); + } +} diff --git a/build/application.linux32/source/links.pde b/build/application.linux32/source/links.pde new file mode 100644 index 0000000..831ad75 --- /dev/null +++ b/build/application.linux32/source/links.pde @@ -0,0 +1,60 @@ +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? + +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. + */ + void update() { + link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; + link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; + } + + 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(); + } +} diff --git a/build/application.linux32/source/nodes.pde b/build/application.linux32/source/nodes.pde new file mode 100644 index 0000000..0f1300d --- /dev/null +++ b/build/application.linux32/source/nodes.pde @@ -0,0 +1,129 @@ +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 + +void addNode(int x, int y, String title) { + nodes[nodeCount] = new Node(x, y, title); + nodeCount++; +} + +void deselect() { + i_selectedNode = -1; +} + +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; + boolean hover() { + if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { + return true; + } else { + return false; + } + } + 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; + } + } + 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(); + } + + void setVectors() { + outletCenter.x = x; + outletCenter.y = y-(padding+textSize/2)*1.3; + inletCenter.x = x; + inletCenter.y = y+(padding+textSize/2)*1.3; + } + + /** + * Called when changing nodes title or when zooming via mouse-wheel + */ + void refreshSize() { + w = int(textWidth(title))+2*padding; + h = textSize+2*padding; + } + + /** + * Update-function is really only necessary when dragging the node + */ + void update() { + if (drag) { + x -= dragMouseStartPoint.x - mouseX; + dragMouseStartPoint.x = mouseX; + y -= dragMouseStartPoint.y - mouseY; + dragMouseStartPoint.y = mouseY; + setVectors(); + } + } + + 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); + } +} diff --git a/build/application.linux32/source/ontology.java b/build/application.linux32/source/ontology.java new file mode 100644 index 0000000..f7bfd3a --- /dev/null +++ b/build/application.linux32/source/ontology.java @@ -0,0 +1,577 @@ +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); + } + } +} diff --git a/build/application.linux32/source/ontology.pde b/build/application.linux32/source/ontology.pde new file mode 100644 index 0000000..07edee4 --- /dev/null +++ b/build/application.linux32/source/ontology.pde @@ -0,0 +1,83 @@ +Node nodes[] = new Node[1000]; +Link links[] = new Link[6000]; + +int textSize = 14; +boolean darkMode = true; + +void setup() { + size(800, 600); + 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"); + } +} + +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); + } + } +} diff --git a/build/application.linux32/source/save_load.pde b/build/application.linux32/source/save_load.pde new file mode 100644 index 0000000..eb7501b --- /dev/null +++ b/build/application.linux32/source/save_load.pde @@ -0,0 +1,74 @@ +/** + * 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"); + +void savePNGFile(File selection) { + savePNGpath = selection.getPath(); + exporting = true; +} + +void saveCSVFile(File selection) { + saveCSV(selection.getPath()); +} + +void loadCSVFile(File selection) { + loadCSV(selection.getPath()); +} + +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(int(nodeData[1]), int(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, int(nodeData[j])); + } + } +} + +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); +} diff --git a/build/application.linux32/source/ui.pde b/build/application.linux32/source/ui.pde new file mode 100644 index 0000000..67f9b3a --- /dev/null +++ b/build/application.linux32/source/ui.pde @@ -0,0 +1,124 @@ +/** + * Keys only get caught for changing a nodes title. + * Previous title is remembered in savedTitle and restored when hitting Escape + */ +String savedTitle; +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(); + } + } +} + + +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 + */ +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; +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(); + } +} diff --git a/build/application.linux64/data/save.csv b/build/application.linux64/data/save.csv new file mode 100644 index 0000000..4ed18e7 --- /dev/null +++ b/build/application.linux64/data/save.csv @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Biological Process,395,537,32,33 +Physiological Process,292,455,34 +Cellular Process,483,455,34 +Cellular Physiological Process,391,375,36,35 +Cell Cycle,305,296,40,41 +Cell Division,544,265,37 +Cytokinesis,543,165,38 +Cytokinesis after Meiosis,399,54 +M Phase of Meiotic Cell Cycle,310,144,38 +M Phase,239,225,39 +Meiotic Cell Cycle,392,225,39 + diff --git a/build/application.linux64/lib/core.jar b/build/application.linux64/lib/core.jar new file mode 100644 index 0000000..efabaad Binary files /dev/null and b/build/application.linux64/lib/core.jar differ diff --git a/build/application.linux64/lib/gluegen-rt-natives-linux-amd64.jar b/build/application.linux64/lib/gluegen-rt-natives-linux-amd64.jar new file mode 100644 index 0000000..a2466f4 Binary files /dev/null and b/build/application.linux64/lib/gluegen-rt-natives-linux-amd64.jar differ diff --git a/build/application.linux64/lib/gluegen-rt.jar b/build/application.linux64/lib/gluegen-rt.jar new file mode 100644 index 0000000..742fdb2 Binary files /dev/null and b/build/application.linux64/lib/gluegen-rt.jar differ diff --git a/build/application.linux64/lib/jogl-all-natives-linux-amd64.jar b/build/application.linux64/lib/jogl-all-natives-linux-amd64.jar new file mode 100644 index 0000000..e57b8c7 Binary files /dev/null and b/build/application.linux64/lib/jogl-all-natives-linux-amd64.jar differ diff --git a/build/application.linux64/lib/jogl-all.jar b/build/application.linux64/lib/jogl-all.jar new file mode 100644 index 0000000..8925b00 Binary files /dev/null and b/build/application.linux64/lib/jogl-all.jar differ diff --git a/build/application.linux64/lib/ontology.jar b/build/application.linux64/lib/ontology.jar new file mode 100644 index 0000000..8379e9f Binary files /dev/null and b/build/application.linux64/lib/ontology.jar differ diff --git a/build/application.linux64/ontology b/build/application.linux64/ontology new file mode 100755 index 0000000..526ebee --- /dev/null +++ b/build/application.linux64/ontology @@ -0,0 +1,5 @@ +#!/bin/sh + +APPDIR=$(readlink -f "$0") +APPDIR=$(dirname "$APPDIR") +java -Xms64m -Xmx12000m -Djna.nosys=true -Djava.library.path="$APPDIR:$APPDIR/lib" -cp "$APPDIR:$APPDIR/lib/ontology.jar:$APPDIR/lib/core.jar:$APPDIR/lib/jogl-all.jar:$APPDIR/lib/gluegen-rt.jar:$APPDIR/lib/jogl-all-natives-linux-amd64.jar:$APPDIR/lib/gluegen-rt-natives-linux-amd64.jar" ontology "$@" diff --git a/build/application.linux64/source/buttons.pde b/build/application.linux64/source/buttons.pde new file mode 100644 index 0000000..525ddb1 --- /dev/null +++ b/build/application.linux64/source/buttons.pde @@ -0,0 +1,78 @@ +int buttonCount = 6; +int i_buttonId; + +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");*/ +} + +void buttonFunctions(int functionID) { + switch (functionID) { + case(0): + addNode(int(random(50, width-50)), int(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; + + boolean hover() { + return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; + } + + void click() { + buttonFunctions(id); + } + Button(int x_, int y_, String label_) { + id = i_buttonId; + i_buttonId++; + x = x_; + y = y_ ; + label = label_; + w = int(textWidth(label))+18; + h = 32; + } + + 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); + } +} diff --git a/build/application.linux64/source/links.pde b/build/application.linux64/source/links.pde new file mode 100644 index 0000000..831ad75 --- /dev/null +++ b/build/application.linux64/source/links.pde @@ -0,0 +1,60 @@ +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? + +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. + */ + void update() { + link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; + link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; + } + + 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(); + } +} diff --git a/build/application.linux64/source/nodes.pde b/build/application.linux64/source/nodes.pde new file mode 100644 index 0000000..0f1300d --- /dev/null +++ b/build/application.linux64/source/nodes.pde @@ -0,0 +1,129 @@ +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 + +void addNode(int x, int y, String title) { + nodes[nodeCount] = new Node(x, y, title); + nodeCount++; +} + +void deselect() { + i_selectedNode = -1; +} + +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; + boolean hover() { + if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { + return true; + } else { + return false; + } + } + 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; + } + } + 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(); + } + + void setVectors() { + outletCenter.x = x; + outletCenter.y = y-(padding+textSize/2)*1.3; + inletCenter.x = x; + inletCenter.y = y+(padding+textSize/2)*1.3; + } + + /** + * Called when changing nodes title or when zooming via mouse-wheel + */ + void refreshSize() { + w = int(textWidth(title))+2*padding; + h = textSize+2*padding; + } + + /** + * Update-function is really only necessary when dragging the node + */ + void update() { + if (drag) { + x -= dragMouseStartPoint.x - mouseX; + dragMouseStartPoint.x = mouseX; + y -= dragMouseStartPoint.y - mouseY; + dragMouseStartPoint.y = mouseY; + setVectors(); + } + } + + 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); + } +} diff --git a/build/application.linux64/source/ontology.java b/build/application.linux64/source/ontology.java new file mode 100644 index 0000000..f7bfd3a --- /dev/null +++ b/build/application.linux64/source/ontology.java @@ -0,0 +1,577 @@ +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); + } + } +} diff --git a/build/application.linux64/source/ontology.pde b/build/application.linux64/source/ontology.pde new file mode 100644 index 0000000..07edee4 --- /dev/null +++ b/build/application.linux64/source/ontology.pde @@ -0,0 +1,83 @@ +Node nodes[] = new Node[1000]; +Link links[] = new Link[6000]; + +int textSize = 14; +boolean darkMode = true; + +void setup() { + size(800, 600); + 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"); + } +} + +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); + } + } +} diff --git a/build/application.linux64/source/save_load.pde b/build/application.linux64/source/save_load.pde new file mode 100644 index 0000000..eb7501b --- /dev/null +++ b/build/application.linux64/source/save_load.pde @@ -0,0 +1,74 @@ +/** + * 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"); + +void savePNGFile(File selection) { + savePNGpath = selection.getPath(); + exporting = true; +} + +void saveCSVFile(File selection) { + saveCSV(selection.getPath()); +} + +void loadCSVFile(File selection) { + loadCSV(selection.getPath()); +} + +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(int(nodeData[1]), int(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, int(nodeData[j])); + } + } +} + +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); +} diff --git a/build/application.linux64/source/ui.pde b/build/application.linux64/source/ui.pde new file mode 100644 index 0000000..67f9b3a --- /dev/null +++ b/build/application.linux64/source/ui.pde @@ -0,0 +1,124 @@ +/** + * Keys only get caught for changing a nodes title. + * Previous title is remembered in savedTitle and restored when hitting Escape + */ +String savedTitle; +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(); + } + } +} + + +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 + */ +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; +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(); + } +} diff --git a/build/application.windows32/data/save.csv b/build/application.windows32/data/save.csv new file mode 100644 index 0000000..4ed18e7 --- /dev/null +++ b/build/application.windows32/data/save.csv @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Biological Process,395,537,32,33 +Physiological Process,292,455,34 +Cellular Process,483,455,34 +Cellular Physiological Process,391,375,36,35 +Cell Cycle,305,296,40,41 +Cell Division,544,265,37 +Cytokinesis,543,165,38 +Cytokinesis after Meiosis,399,54 +M Phase of Meiotic Cell Cycle,310,144,38 +M Phase,239,225,39 +Meiotic Cell Cycle,392,225,39 + diff --git a/build/application.windows32/lib/core.jar b/build/application.windows32/lib/core.jar new file mode 100644 index 0000000..efabaad Binary files /dev/null and b/build/application.windows32/lib/core.jar differ diff --git a/build/application.windows32/lib/gluegen-rt-natives-windows-i586.jar b/build/application.windows32/lib/gluegen-rt-natives-windows-i586.jar new file mode 100644 index 0000000..1c393b7 Binary files /dev/null and b/build/application.windows32/lib/gluegen-rt-natives-windows-i586.jar differ diff --git a/build/application.windows32/lib/gluegen-rt.jar b/build/application.windows32/lib/gluegen-rt.jar new file mode 100644 index 0000000..742fdb2 Binary files /dev/null and b/build/application.windows32/lib/gluegen-rt.jar differ diff --git a/build/application.windows32/lib/jogl-all-natives-windows-i586.jar b/build/application.windows32/lib/jogl-all-natives-windows-i586.jar new file mode 100644 index 0000000..4439f1d Binary files /dev/null and b/build/application.windows32/lib/jogl-all-natives-windows-i586.jar differ diff --git a/build/application.windows32/lib/jogl-all.jar b/build/application.windows32/lib/jogl-all.jar new file mode 100644 index 0000000..8925b00 Binary files /dev/null and b/build/application.windows32/lib/jogl-all.jar differ diff --git a/build/application.windows32/lib/ontology.jar b/build/application.windows32/lib/ontology.jar new file mode 100644 index 0000000..8379e9f Binary files /dev/null and b/build/application.windows32/lib/ontology.jar differ diff --git a/build/application.windows32/ontology.exe b/build/application.windows32/ontology.exe new file mode 100755 index 0000000..40790d1 Binary files /dev/null and b/build/application.windows32/ontology.exe differ diff --git a/build/application.windows32/source/buttons.pde b/build/application.windows32/source/buttons.pde new file mode 100644 index 0000000..525ddb1 --- /dev/null +++ b/build/application.windows32/source/buttons.pde @@ -0,0 +1,78 @@ +int buttonCount = 6; +int i_buttonId; + +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");*/ +} + +void buttonFunctions(int functionID) { + switch (functionID) { + case(0): + addNode(int(random(50, width-50)), int(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; + + boolean hover() { + return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; + } + + void click() { + buttonFunctions(id); + } + Button(int x_, int y_, String label_) { + id = i_buttonId; + i_buttonId++; + x = x_; + y = y_ ; + label = label_; + w = int(textWidth(label))+18; + h = 32; + } + + 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); + } +} diff --git a/build/application.windows32/source/links.pde b/build/application.windows32/source/links.pde new file mode 100644 index 0000000..831ad75 --- /dev/null +++ b/build/application.windows32/source/links.pde @@ -0,0 +1,60 @@ +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? + +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. + */ + void update() { + link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; + link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; + } + + 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(); + } +} diff --git a/build/application.windows32/source/nodes.pde b/build/application.windows32/source/nodes.pde new file mode 100644 index 0000000..0f1300d --- /dev/null +++ b/build/application.windows32/source/nodes.pde @@ -0,0 +1,129 @@ +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 + +void addNode(int x, int y, String title) { + nodes[nodeCount] = new Node(x, y, title); + nodeCount++; +} + +void deselect() { + i_selectedNode = -1; +} + +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; + boolean hover() { + if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { + return true; + } else { + return false; + } + } + 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; + } + } + 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(); + } + + void setVectors() { + outletCenter.x = x; + outletCenter.y = y-(padding+textSize/2)*1.3; + inletCenter.x = x; + inletCenter.y = y+(padding+textSize/2)*1.3; + } + + /** + * Called when changing nodes title or when zooming via mouse-wheel + */ + void refreshSize() { + w = int(textWidth(title))+2*padding; + h = textSize+2*padding; + } + + /** + * Update-function is really only necessary when dragging the node + */ + void update() { + if (drag) { + x -= dragMouseStartPoint.x - mouseX; + dragMouseStartPoint.x = mouseX; + y -= dragMouseStartPoint.y - mouseY; + dragMouseStartPoint.y = mouseY; + setVectors(); + } + } + + 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); + } +} diff --git a/build/application.windows32/source/ontology.java b/build/application.windows32/source/ontology.java new file mode 100644 index 0000000..f7bfd3a --- /dev/null +++ b/build/application.windows32/source/ontology.java @@ -0,0 +1,577 @@ +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); + } + } +} diff --git a/build/application.windows32/source/ontology.pde b/build/application.windows32/source/ontology.pde new file mode 100644 index 0000000..07edee4 --- /dev/null +++ b/build/application.windows32/source/ontology.pde @@ -0,0 +1,83 @@ +Node nodes[] = new Node[1000]; +Link links[] = new Link[6000]; + +int textSize = 14; +boolean darkMode = true; + +void setup() { + size(800, 600); + 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"); + } +} + +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); + } + } +} diff --git a/build/application.windows32/source/save_load.pde b/build/application.windows32/source/save_load.pde new file mode 100644 index 0000000..eb7501b --- /dev/null +++ b/build/application.windows32/source/save_load.pde @@ -0,0 +1,74 @@ +/** + * 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"); + +void savePNGFile(File selection) { + savePNGpath = selection.getPath(); + exporting = true; +} + +void saveCSVFile(File selection) { + saveCSV(selection.getPath()); +} + +void loadCSVFile(File selection) { + loadCSV(selection.getPath()); +} + +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(int(nodeData[1]), int(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, int(nodeData[j])); + } + } +} + +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); +} diff --git a/build/application.windows32/source/ui.pde b/build/application.windows32/source/ui.pde new file mode 100644 index 0000000..67f9b3a --- /dev/null +++ b/build/application.windows32/source/ui.pde @@ -0,0 +1,124 @@ +/** + * Keys only get caught for changing a nodes title. + * Previous title is remembered in savedTitle and restored when hitting Escape + */ +String savedTitle; +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(); + } + } +} + + +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 + */ +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; +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(); + } +} diff --git a/build/application.windows64/data/save.csv b/build/application.windows64/data/save.csv new file mode 100644 index 0000000..4ed18e7 --- /dev/null +++ b/build/application.windows64/data/save.csv @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Biological Process,395,537,32,33 +Physiological Process,292,455,34 +Cellular Process,483,455,34 +Cellular Physiological Process,391,375,36,35 +Cell Cycle,305,296,40,41 +Cell Division,544,265,37 +Cytokinesis,543,165,38 +Cytokinesis after Meiosis,399,54 +M Phase of Meiotic Cell Cycle,310,144,38 +M Phase,239,225,39 +Meiotic Cell Cycle,392,225,39 + diff --git a/build/application.windows64/lib/core.jar b/build/application.windows64/lib/core.jar new file mode 100644 index 0000000..efabaad Binary files /dev/null and b/build/application.windows64/lib/core.jar differ diff --git a/build/application.windows64/lib/gluegen-rt-natives-windows-amd64.jar b/build/application.windows64/lib/gluegen-rt-natives-windows-amd64.jar new file mode 100644 index 0000000..517fb84 Binary files /dev/null and b/build/application.windows64/lib/gluegen-rt-natives-windows-amd64.jar differ diff --git a/build/application.windows64/lib/gluegen-rt.jar b/build/application.windows64/lib/gluegen-rt.jar new file mode 100644 index 0000000..742fdb2 Binary files /dev/null and b/build/application.windows64/lib/gluegen-rt.jar differ diff --git a/build/application.windows64/lib/jogl-all-natives-windows-amd64.jar b/build/application.windows64/lib/jogl-all-natives-windows-amd64.jar new file mode 100644 index 0000000..9577bf1 Binary files /dev/null and b/build/application.windows64/lib/jogl-all-natives-windows-amd64.jar differ diff --git a/build/application.windows64/lib/jogl-all.jar b/build/application.windows64/lib/jogl-all.jar new file mode 100644 index 0000000..8925b00 Binary files /dev/null and b/build/application.windows64/lib/jogl-all.jar differ diff --git a/build/application.windows64/lib/ontology.jar b/build/application.windows64/lib/ontology.jar new file mode 100644 index 0000000..8379e9f Binary files /dev/null and b/build/application.windows64/lib/ontology.jar differ diff --git a/build/application.windows64/ontology.exe b/build/application.windows64/ontology.exe new file mode 100755 index 0000000..3d0c330 Binary files /dev/null and b/build/application.windows64/ontology.exe differ diff --git a/build/application.windows64/source/buttons.pde b/build/application.windows64/source/buttons.pde new file mode 100644 index 0000000..525ddb1 --- /dev/null +++ b/build/application.windows64/source/buttons.pde @@ -0,0 +1,78 @@ +int buttonCount = 6; +int i_buttonId; + +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");*/ +} + +void buttonFunctions(int functionID) { + switch (functionID) { + case(0): + addNode(int(random(50, width-50)), int(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; + + boolean hover() { + return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; + } + + void click() { + buttonFunctions(id); + } + Button(int x_, int y_, String label_) { + id = i_buttonId; + i_buttonId++; + x = x_; + y = y_ ; + label = label_; + w = int(textWidth(label))+18; + h = 32; + } + + 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); + } +} diff --git a/build/application.windows64/source/links.pde b/build/application.windows64/source/links.pde new file mode 100644 index 0000000..831ad75 --- /dev/null +++ b/build/application.windows64/source/links.pde @@ -0,0 +1,60 @@ +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? + +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. + */ + void update() { + link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; + link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; + } + + 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(); + } +} diff --git a/build/application.windows64/source/nodes.pde b/build/application.windows64/source/nodes.pde new file mode 100644 index 0000000..0f1300d --- /dev/null +++ b/build/application.windows64/source/nodes.pde @@ -0,0 +1,129 @@ +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 + +void addNode(int x, int y, String title) { + nodes[nodeCount] = new Node(x, y, title); + nodeCount++; +} + +void deselect() { + i_selectedNode = -1; +} + +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; + boolean hover() { + if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { + return true; + } else { + return false; + } + } + 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; + } + } + 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(); + } + + void setVectors() { + outletCenter.x = x; + outletCenter.y = y-(padding+textSize/2)*1.3; + inletCenter.x = x; + inletCenter.y = y+(padding+textSize/2)*1.3; + } + + /** + * Called when changing nodes title or when zooming via mouse-wheel + */ + void refreshSize() { + w = int(textWidth(title))+2*padding; + h = textSize+2*padding; + } + + /** + * Update-function is really only necessary when dragging the node + */ + void update() { + if (drag) { + x -= dragMouseStartPoint.x - mouseX; + dragMouseStartPoint.x = mouseX; + y -= dragMouseStartPoint.y - mouseY; + dragMouseStartPoint.y = mouseY; + setVectors(); + } + } + + 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); + } +} diff --git a/build/application.windows64/source/ontology.java b/build/application.windows64/source/ontology.java new file mode 100644 index 0000000..f7bfd3a --- /dev/null +++ b/build/application.windows64/source/ontology.java @@ -0,0 +1,577 @@ +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); + } + } +} diff --git a/build/application.windows64/source/ontology.pde b/build/application.windows64/source/ontology.pde new file mode 100644 index 0000000..07edee4 --- /dev/null +++ b/build/application.windows64/source/ontology.pde @@ -0,0 +1,83 @@ +Node nodes[] = new Node[1000]; +Link links[] = new Link[6000]; + +int textSize = 14; +boolean darkMode = true; + +void setup() { + size(800, 600); + 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"); + } +} + +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); + } + } +} diff --git a/build/application.windows64/source/save_load.pde b/build/application.windows64/source/save_load.pde new file mode 100644 index 0000000..eb7501b --- /dev/null +++ b/build/application.windows64/source/save_load.pde @@ -0,0 +1,74 @@ +/** + * 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"); + +void savePNGFile(File selection) { + savePNGpath = selection.getPath(); + exporting = true; +} + +void saveCSVFile(File selection) { + saveCSV(selection.getPath()); +} + +void loadCSVFile(File selection) { + loadCSV(selection.getPath()); +} + +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(int(nodeData[1]), int(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, int(nodeData[j])); + } + } +} + +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); +} diff --git a/build/application.windows64/source/ui.pde b/build/application.windows64/source/ui.pde new file mode 100644 index 0000000..67f9b3a --- /dev/null +++ b/build/application.windows64/source/ui.pde @@ -0,0 +1,124 @@ +/** + * Keys only get caught for changing a nodes title. + * Previous title is remembered in savedTitle and restored when hitting Escape + */ +String savedTitle; +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(); + } + } +} + + +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 + */ +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; +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(); + } +} diff --git a/buttons.pde b/buttons.pde new file mode 100644 index 0000000..525ddb1 --- /dev/null +++ b/buttons.pde @@ -0,0 +1,78 @@ +int buttonCount = 6; +int i_buttonId; + +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");*/ +} + +void buttonFunctions(int functionID) { + switch (functionID) { + case(0): + addNode(int(random(50, width-50)), int(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; + + boolean hover() { + return (mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) ? true : false; + } + + void click() { + buttonFunctions(id); + } + Button(int x_, int y_, String label_) { + id = i_buttonId; + i_buttonId++; + x = x_; + y = y_ ; + label = label_; + w = int(textWidth(label))+18; + h = 32; + } + + 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); + } +} diff --git a/data/save.csv b/data/save.csv new file mode 100644 index 0000000..4ed18e7 --- /dev/null +++ b/data/save.csv @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Biological Process,395,537,32,33 +Physiological Process,292,455,34 +Cellular Process,483,455,34 +Cellular Physiological Process,391,375,36,35 +Cell Cycle,305,296,40,41 +Cell Division,544,265,37 +Cytokinesis,543,165,38 +Cytokinesis after Meiosis,399,54 +M Phase of Meiotic Cell Cycle,310,144,38 +M Phase,239,225,39 +Meiotic Cell Cycle,392,225,39 + diff --git a/examples/example_bright.png b/examples/example_bright.png new file mode 100644 index 0000000..a8fa864 Binary files /dev/null and b/examples/example_bright.png differ diff --git a/examples/gui_dark.png b/examples/gui_dark.png new file mode 100644 index 0000000..95a8e57 Binary files /dev/null and b/examples/gui_dark.png differ diff --git a/links.pde b/links.pde new file mode 100644 index 0000000..831ad75 --- /dev/null +++ b/links.pde @@ -0,0 +1,60 @@ +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? + +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. + */ + void update() { + link.x = nodes[targetNode].inletCenter.x - nodes[parentNode].outletCenter.x; + link.y = nodes[targetNode].inletCenter.y - nodes[parentNode].outletCenter.y; + } + + 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(); + } +} diff --git a/nodes.pde b/nodes.pde new file mode 100644 index 0000000..0f1300d --- /dev/null +++ b/nodes.pde @@ -0,0 +1,129 @@ +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 + +void addNode(int x, int y, String title) { + nodes[nodeCount] = new Node(x, y, title); + nodeCount++; +} + +void deselect() { + i_selectedNode = -1; +} + +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; + boolean hover() { + if (mouseX > x-w/2 && mouseX < x+w/2 && mouseY > y-h/2 && mouseY < y+h/2) { + return true; + } else { + return false; + } + } + 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; + } + } + 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(); + } + + void setVectors() { + outletCenter.x = x; + outletCenter.y = y-(padding+textSize/2)*1.3; + inletCenter.x = x; + inletCenter.y = y+(padding+textSize/2)*1.3; + } + + /** + * Called when changing nodes title or when zooming via mouse-wheel + */ + void refreshSize() { + w = int(textWidth(title))+2*padding; + h = textSize+2*padding; + } + + /** + * Update-function is really only necessary when dragging the node + */ + void update() { + if (drag) { + x -= dragMouseStartPoint.x - mouseX; + dragMouseStartPoint.x = mouseX; + y -= dragMouseStartPoint.y - mouseY; + dragMouseStartPoint.y = mouseY; + setVectors(); + } + } + + 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); + } +} diff --git a/ontology.pde b/ontology.pde new file mode 100644 index 0000000..07edee4 --- /dev/null +++ b/ontology.pde @@ -0,0 +1,83 @@ +Node nodes[] = new Node[1000]; +Link links[] = new Link[6000]; + +int textSize = 14; +boolean darkMode = true; + +void setup() { + size(800, 600); + 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"); + } +} + +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); + } + } +} diff --git a/save_load.pde b/save_load.pde new file mode 100644 index 0000000..eb7501b --- /dev/null +++ b/save_load.pde @@ -0,0 +1,74 @@ +/** + * 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"); + +void savePNGFile(File selection) { + savePNGpath = selection.getPath(); + exporting = true; +} + +void saveCSVFile(File selection) { + saveCSV(selection.getPath()); +} + +void loadCSVFile(File selection) { + loadCSV(selection.getPath()); +} + +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(int(nodeData[1]), int(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, int(nodeData[j])); + } + } +} + +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); +} diff --git a/ui.pde b/ui.pde new file mode 100644 index 0000000..67f9b3a --- /dev/null +++ b/ui.pde @@ -0,0 +1,124 @@ +/** + * Keys only get caught for changing a nodes title. + * Previous title is remembered in savedTitle and restored when hitting Escape + */ +String savedTitle; +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(); + } + } +} + + +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 + */ +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; +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(); + } +}