Skip to content
Snippets Groups Projects
Commit 4410934c authored by Kim Kern's avatar Kim Kern
Browse files

merge dev-group3-gephi

parents 5d9d9fa7 bc93e370
No related branches found
No related tags found
No related merge requests found
Showing
with 787 additions and 57 deletions
...@@ -56,5 +56,12 @@ ...@@ -56,5 +56,12 @@
<artifactId>reflections</artifactId> <artifactId>reflections</artifactId>
<version>0.9.10</version> <version>0.9.10</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.gephi/gephi-toolkit -->
<dependency>
<groupId>org.gephi</groupId>
<artifactId>gephi-toolkit</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies> </dependencies>
</project> </project>
\ No newline at end of file
...@@ -114,6 +114,10 @@ public abstract class AbstractNode extends UntypedActor implements Serializable ...@@ -114,6 +114,10 @@ public abstract class AbstractNode extends UntypedActor implements Serializable
excludedNeighbors.clear(); excludedNeighbors.clear();
} }
protected Collection<ActorRef> getExcludedNeighbors() {
return excludedNeighbors;
}
/** /**
* Returns true, if all neighbors are excluded. * Returns true, if all neighbors are excluded.
* *
......
package fucoin.actions.control;
import akka.actor.ActorRef;
import akka.actor.UntypedActorContext;
import fucoin.actions.ClientAction;
import fucoin.wallet.AbstractWallet;
import java.util.Collections;
import java.util.List;
/**
* @author davidbohn
*/
public class ActionAddOverlayNeighbours extends ClientAction {
protected List<ActorRef> neighbours;
public ActionAddOverlayNeighbours(List<ActorRef> neighbours) {
this.neighbours = neighbours;
}
@Override
protected void onAction(ActorRef sender, ActorRef self, UntypedActorContext context, AbstractWallet abstractNode) {
neighbours.stream().forEach(abstractNode::addOverlayNeighbour);
}
}
...@@ -28,7 +28,7 @@ public class ActionJoinAnswer extends ClientAction { ...@@ -28,7 +28,7 @@ public class ActionJoinAnswer extends ClientAction {
protected void onAction(ActorRef sender, ActorRef self, protected void onAction(ActorRef sender, ActorRef self,
UntypedActorContext context, AbstractWallet wallet) { UntypedActorContext context, AbstractWallet wallet) {
wallet.addLogMsg("Addressed to " + self.path().name() + " from " + sender.path().name() + ": someNeighbors:" + someNeighbors); //wallet.addLogMsg("Addressed to " + self.path().name() + " from " + sender.path().name() + ": someNeighbors:" + someNeighbors);
// your neighbours? my neighbours! // your neighbours? my neighbours!
for (Entry<String, ActorRef> neighbor : someNeighbors.entrySet()) { for (Entry<String, ActorRef> neighbor : someNeighbors.entrySet()) {
......
...@@ -43,7 +43,7 @@ public class ActionCommitDistributedCommittedTransfer extends ClientAction { ...@@ -43,7 +43,7 @@ public class ActionCommitDistributedCommittedTransfer extends ClientAction {
@Override @Override
protected void onAction(ActorRef sender, ActorRef self, protected void onAction(ActorRef sender, ActorRef self,
UntypedActorContext context, AbstractWallet wallet) { UntypedActorContext context, AbstractWallet wallet) {
wallet.addLogMsg("ActionCommitDistributedCommittedTransfer is granted? " + granted); //wallet.addLogMsg("ActionCommitDistributedCommittedTransfer is granted? " + granted);
if (granted) { if (granted) {
if (source.compareTo(self) == 0) { if (source.compareTo(self) == 0) {
...@@ -75,7 +75,6 @@ public class ActionCommitDistributedCommittedTransfer extends ClientAction { ...@@ -75,7 +75,6 @@ public class ActionCommitDistributedCommittedTransfer extends ClientAction {
} }
} }
//wallet.addLogMsg("wallet.amounts:" + wallet.amounts);
} }
} }
...@@ -28,9 +28,9 @@ public class ActionInvokeDistributedCommittedTransfer extends CoordinatorTransac ...@@ -28,9 +28,9 @@ public class ActionInvokeDistributedCommittedTransfer extends CoordinatorTransac
@Override @Override
protected void onAction(ActorRef sender, ActorRef self, protected void onAction(ActorRef sender, ActorRef self,
UntypedActorContext context, SuperVisorImpl superVisor) { UntypedActorContext context, SuperVisorImpl superVisor) {
superVisor.addLogMsg("invoke transaction " + source.path().name() + /*superVisor.addLogMsg("invoke transaction " + source.path().name() +
" sends " + amount + " sends " + amount +
" to " + target.path().name()); " to " + target.path().name());*/
long timeout = System.currentTimeMillis() + 500; long timeout = System.currentTimeMillis() + 500;
......
...@@ -28,7 +28,7 @@ public class ActionPrepareDistributedCommittedTransferAnswer extends Coordinator ...@@ -28,7 +28,7 @@ public class ActionPrepareDistributedCommittedTransferAnswer extends Coordinator
protected void onAction(ActorRef sender, ActorRef self, protected void onAction(ActorRef sender, ActorRef self,
UntypedActorContext context, SuperVisorImpl superVisor) { UntypedActorContext context, SuperVisorImpl superVisor) {
superVisor.addLogMsg("granted?" + granted); //superVisor.addLogMsg("granted?" + granted);
DistributedCommittedTransferRequest request = superVisor.getRequest(id); DistributedCommittedTransferRequest request = superVisor.getRequest(id);
......
...@@ -5,6 +5,7 @@ import akka.actor.Props; ...@@ -5,6 +5,7 @@ import akka.actor.Props;
import akka.pattern.Patterns; import akka.pattern.Patterns;
import akka.util.Timeout; import akka.util.Timeout;
import fucoin.AbstractNode; import fucoin.AbstractNode;
import fucoin.actions.control.ActionAddOverlayNeighbours;
import fucoin.actions.control.ActionAnnounceWalletCreation; import fucoin.actions.control.ActionAnnounceWalletCreation;
import fucoin.actions.control.ActionWalletSendMoney; import fucoin.actions.control.ActionWalletSendMoney;
import fucoin.actions.join.ActionTellSupervisor; import fucoin.actions.join.ActionTellSupervisor;
...@@ -14,14 +15,16 @@ import fucoin.actions.transaction.ActionNotifyObserver; ...@@ -14,14 +15,16 @@ import fucoin.actions.transaction.ActionNotifyObserver;
import fucoin.configurations.internal.ConfigurationCreator; import fucoin.configurations.internal.ConfigurationCreator;
import fucoin.supervisor.SuperVisorImpl; import fucoin.supervisor.SuperVisorImpl;
import fucoin.wallet.WalletImpl; import fucoin.wallet.WalletImpl;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.Graph;
import org.gephi.graph.api.Node;
import scala.concurrent.Await; import scala.concurrent.Await;
import scala.concurrent.Future; import scala.concurrent.Future;
import scala.concurrent.duration.Duration; import scala.concurrent.duration.Duration;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
/** /**
* *
...@@ -30,11 +33,14 @@ public abstract class AbstractConfiguration extends AbstractNode { ...@@ -30,11 +33,14 @@ public abstract class AbstractConfiguration extends AbstractNode {
private ActorRef superVisor; private ActorRef superVisor;
private final List<ActorRef> activeActors = new ArrayList<>(); private final HashMap<String, ActorRef> activeActors = new HashMap<>();
private Timeout timeout = new Timeout(Duration.create(10, "seconds")); private Timeout timeout = new Timeout(Duration.create(10, "seconds"));
private int remainingTransactions; private int remainingTransactions;
private Callback callback;
private ActorRef lastWallet = null;
public static Props props(Class configurationClass) { public static Props props(Class configurationClass) {
...@@ -58,21 +64,18 @@ public abstract class AbstractConfiguration extends AbstractNode { ...@@ -58,21 +64,18 @@ public abstract class AbstractConfiguration extends AbstractNode {
*/ */
private ActorRef createWallet(String name, boolean createGUI) { private ActorRef createWallet(String name, boolean createGUI) {
Props props; Props props;
int numOfWallets = activeActors.size();
if (numOfWallets == 0) {
props = WalletImpl.props(null, name, createGUI);
} else {
props = WalletImpl.props(activeActors.get(numOfWallets - 1), name, createGUI);
}
ActorRef actorRef = context().actorOf(props, name); props = WalletImpl.props(lastWallet, name, createGUI);
activeActors.add(actorRef); ActorRef actorRef = context().actorOf(props, name);
activeActors.put(name, actorRef);
if (numOfWallets == 0) { if (lastWallet == null) {
actorRef.tell(new ActionTellSupervisor(superVisor), superVisor); actorRef.tell(new ActionTellSupervisor(superVisor), superVisor);
} }
lastWallet = actorRef;
return actorRef; return actorRef;
} }
...@@ -90,6 +93,15 @@ public abstract class AbstractConfiguration extends AbstractNode { ...@@ -90,6 +93,15 @@ public abstract class AbstractConfiguration extends AbstractNode {
Await.result(future, timeout.duration()); Await.result(future, timeout.duration());
} }
public void spawnWalletsFromNodes(Collection<Node> nodes, boolean createGUI) throws Exception {
Future<Object> future = Patterns.ask(superVisor, new ActionAnnounceWalletCreation(nodes.size(), self()), timeout);
for (Node node : nodes) {
String nameOfTheWallet = node.getLabel();
createWallet(nameOfTheWallet, createGUI);
}
Await.result(future, timeout.duration());
}
/** /**
* Fetch a random wallet * Fetch a random wallet
*/ */
...@@ -98,11 +110,19 @@ public abstract class AbstractConfiguration extends AbstractNode { ...@@ -98,11 +110,19 @@ public abstract class AbstractConfiguration extends AbstractNode {
} }
public List<ActorRef> wallets() { public List<ActorRef> wallets() {
return this.activeActors; return new ArrayList<>(this.activeActors.values());
}
public ActorRef walletByName(String name) {
return activeActors.get(name);
} }
protected void randomTransactions(int number, int maxTransactionsAtTheSameTime) { protected void randomTransactions(int number, int maxTransactionsAtTheSameTime) {
randomTransactions(number, maxTransactionsAtTheSameTime, null);
}
protected void randomTransactions(int number, int maxTransactionsAtTheSameTime, Callback callback) {
this.callback = callback;
remainingTransactions = number; remainingTransactions = number;
for (int i = 0; i < Math.min(number, maxTransactionsAtTheSameTime); i++) { for (int i = 0; i < Math.min(number, maxTransactionsAtTheSameTime); i++) {
...@@ -118,6 +138,7 @@ public abstract class AbstractConfiguration extends AbstractNode { ...@@ -118,6 +138,7 @@ public abstract class AbstractConfiguration extends AbstractNode {
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error while trying to perform a random transaction: " + e.getMessage()); System.err.println("Error while trying to perform a random transaction: " + e.getMessage());
remainingTransactions = 0; remainingTransactions = 0;
callback = null;
} }
} }
...@@ -169,6 +190,11 @@ public abstract class AbstractConfiguration extends AbstractNode { ...@@ -169,6 +190,11 @@ public abstract class AbstractConfiguration extends AbstractNode {
if (remainingTransactions > 0) { if (remainingTransactions > 0) {
nextRandomTransaction(); nextRandomTransaction();
} else {
if (callback != null) {
callback.transactionsFinished();
}
callback = null;
} }
} }
} }
...@@ -181,4 +207,35 @@ public abstract class AbstractConfiguration extends AbstractNode { ...@@ -181,4 +207,35 @@ public abstract class AbstractConfiguration extends AbstractNode {
} }
public abstract void run(); public abstract void run();
protected void createOverlayNetwork(Graph g) {
Collection<Node> nodes = g.getNodes().toCollection();
try {
spawnWalletsFromNodes(nodes, false);
} catch (Exception e) {
e.printStackTrace();
}
nodes.stream().forEach(node -> {
ActorRef wallet = walletByName(node.getLabel());
Edge[] edges = g.getEdges(node).toArray();
// Search for all reachable neighbours of the node
// by filtering the list of incident edges of the node
// and retrieve the respective ActorRef instance
List<ActorRef> overlayNeighbours = Arrays.stream(edges).filter(edge -> !edge.isDirected() || edge.getSource() == node).map(edge -> {
if (edge.getSource() == node) {
return edge.getTarget();
}
return edge.getSource();
}).map(target -> walletByName(target.getLabel())).collect(Collectors.toList());
wallet.tell(new ActionAddOverlayNeighbours(overlayNeighbours), self());
});
}
interface Callback {
public void transactionsFinished();
}
} }
package fucoin.configurations; package fucoin.configurations;
import akka.actor.ActorRef; import akka.actor.ActorRef;
import akka.pattern.Patterns;
import akka.util.Timeout; import akka.util.Timeout;
import fucoin.actions.control.ActionWalletSendMoney; import fucoin.actions.control.ActionWalletSendMoney;
import fucoin.actions.transaction.ActionGetAmount;
import fucoin.actions.transaction.ActionGetAmountAnswer;
import fucoin.actions.transaction.ActionNotifyObserver;
import fucoin.configurations.internal.ConfigurationName; import fucoin.configurations.internal.ConfigurationName;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration; import scala.concurrent.duration.Duration;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
/** /**
......
package fucoin.configurations;
import akka.actor.ActorRef;
import akka.dispatch.OnSuccess;
import akka.pattern.Patterns;
import akka.util.Timeout;
import fucoin.actions.transaction.ActionGetAmount;
import fucoin.actions.transaction.ActionGetAmountAnswer;
import fucoin.configurations.internal.ConfigurationName;
import fucoin.configurations.internal.GephiLoader;
import fucoin.gui.gephi.GephiFileSelector;
import fucoin.gui.gephi.GraphWindow;
import org.gephi.graph.api.Graph;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
@ConfigurationName("Gephi Test Configuration")
public class GephiConfiguration extends AbstractConfiguration {
private Timeout timeout = new Timeout(Duration.create(10, "seconds"));
@Override
public void run() {
initSupervisor();
GephiLoader gephiLoader = new GephiLoader();
GephiFileSelector fileSelector = new GephiFileSelector();
Graph g;
File selectedTopology;
try {
selectedTopology = fileSelector.selectTopology();
g = gephiLoader.loadFile(selectedTopology);
} catch (URISyntaxException | IOException e) {
e.printStackTrace();
return;
}
createOverlayNetwork(g);
GraphWindow graphWindow = new GraphWindow();
graphWindow.setDisplayedFilename(selectedTopology.getName());
graphWindow.setVisible(true);
// add a click listener for displaying further information about a wallet when clicking on a node
graphWindow.addNodeClickHandler((node, event) -> {
// get associated wallet and ask for its amount
ActorRef wallet = walletByName(node.getLabel());
Future<Object> future = Patterns.ask(wallet, new ActionGetAmount(), timeout);
future.onSuccess(new OnSuccess<Object>() {
@Override
public void onSuccess(Object result) throws Throwable {
// display the amount when an answer is received
ActionGetAmountAnswer answer = (ActionGetAmountAnswer) result;
graphWindow.setInfobarText(node.getLabel()+" has "+answer.amount+" FUCs");
}
}, context().dispatcher());
});
}
}
package fucoin.configurations;
import akka.actor.ActorRef;
import fucoin.configurations.internal.ConfigurationName;
/**
* This configuration spawns 200 wallets to demonstrate the spawning of many headless wallets
*/
@ConfigurationName("Lots of Wallets")
public class MassWalletConfiguration extends AbstractConfiguration {
@Override
public void run() {
ActorRef supervisor = initSupervisor();
try {
spawnWallets(10, false);
System.out.println("Wallet spawning done!");
} catch (Exception e) {
System.out.println("Wallet spawning timed out!");
}
randomTransactions(10, 10);
}
@Override
public void onReceive(Object message) {
super.onReceive(message);
}
}
package fucoin.configurations.internal;
import org.gephi.graph.api.Graph;
import org.gephi.graph.api.GraphController;
import org.gephi.io.importer.api.Container;
import org.gephi.io.importer.api.ImportController;
import org.gephi.io.processor.plugin.DefaultProcessor;
import org.gephi.project.api.ProjectController;
import org.gephi.project.api.Workspace;
import org.openide.util.Lookup;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URISyntaxException;
public class GephiLoader {
private Workspace workspace;
/**
* Load a graph file that is in the resources directory of the application
*
* @throws URISyntaxException
* @throws FileNotFoundException
*/
public Graph loadFileFromResources(String path) throws URISyntaxException, FileNotFoundException {
return loadFile(new File(getClass().getResource(path).toURI()));
}
/**
* Initialize the Gephi toolkit and load the provided file
*
* @throws FileNotFoundException
*/
public Graph loadFile(File file) throws FileNotFoundException {
ProjectController pc = Lookup.getDefault().lookup(ProjectController.class);
pc.newProject();
workspace = pc.getCurrentWorkspace();
Container container;
ImportController importController = Lookup.getDefault().lookup(ImportController.class);
container = importController.importFile(file);
importController.process(container, new DefaultProcessor(), workspace);
return Lookup.getDefault().lookup(GraphController.class).getGraphModel(workspace).getGraph();
}
public Workspace getWorkspace() {
return workspace;
}
}
package fucoin.gui.gephi;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
public class GephiFileSelector {
/**
* Display a file selector to select a gephi graph file
* @return the selected file or the first graph in the resources directory if none selected
* @throws IOException
* @throws URISyntaxException
*/
public File selectTopology() throws IOException, URISyntaxException {
JFileChooser fileChooser = new JFileChooser(new File(getClass().getResource("/").toURI()));
fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
FileNameExtensionFilter fileNameExtensionFilter = new FileNameExtensionFilter("Graph Files", "gexf");
fileChooser.setFileFilter(fileNameExtensionFilter);
int result = fileChooser.showOpenDialog(null);
if (result == JFileChooser.APPROVE_OPTION) {
return fileChooser.getSelectedFile();
}
return getBundledTopologies().get(0);
}
/**
* Get a list of all bundled topology files (i.e. that are stored in the resources)
* @throws URISyntaxException
* @throws IOException
*/
public List<File> getBundledTopologies() throws URISyntaxException, IOException {
return Files.list(Paths.get(getClass().getResource("/").toURI()))
.filter(Files::isRegularFile)
.filter(path -> path.toString().toLowerCase().endsWith(".gexf"))
.map(Path::toFile).collect(Collectors.toList());
}
}
package fucoin.gui.gephi;
import org.gephi.graph.api.Node;
import org.gephi.preview.api.*;
import org.gephi.preview.types.DependantOriginalColor;
import org.gephi.project.api.Workspace;
import org.openide.util.Lookup;
import javax.swing.*;
import java.awt.*;
import java.lang.reflect.Field;
import java.util.*;
import java.util.List;
import java.util.Timer;
public class GraphWindow extends JFrame implements NodeMouseListener {
protected List<NodeClickHandler> clickHandlers = new ArrayList<>();
private final PreviewController previewController;
private final PreviewSketch previewSketch;
private JLabel infobarText;
private Timer timer;
private final String baseWindowTitle = "Network Overlay Graph";
private final String defaultInfoBarText = "Click on a node to see further information.";
public GraphWindow() {
super();
timer = new Timer();
setTitle(baseWindowTitle);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
previewController = Lookup.getDefault().lookup(PreviewController.class);
PreviewModel previewModel = previewController.getModel();
previewModel.getProperties().putValue(PreviewProperty.SHOW_NODE_LABELS, Boolean.TRUE);
previewModel.getProperties().putValue(PreviewProperty.NODE_LABEL_COLOR, new DependantOriginalColor(Color.BLACK));
Font labelFont = new Font("Verdana", Font.PLAIN, 5);
previewModel.getProperties().putValue(PreviewProperty.NODE_LABEL_FONT, labelFont);
previewModel.getProperties().putValue(PreviewProperty.EDGE_CURVED, Boolean.FALSE);
previewModel.getProperties().putValue(PreviewProperty.EDGE_OPACITY, 50);
previewModel.getProperties().putValue(PreviewProperty.EDGE_RADIUS, 10f);
previewModel.getProperties().putValue("graphWindow.mouse.handler", this);
G2DTarget target = (G2DTarget) previewController.getRenderTarget(RenderTarget.G2D_TARGET);
previewSketch = new PreviewSketch(target, isRetina());
infobarText = new JLabel(defaultInfoBarText, SwingConstants.LEFT);
this.add(previewSketch, BorderLayout.CENTER);
JPanel infobar = new JPanel(new BorderLayout());
infobar.add(infobarText, BorderLayout.WEST);
JPanel zoomOptions = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 2));
zoomOptions.add(new JLabel("Zoom: "));
Dimension zoomBtnSize = new Dimension(20, 20);
JButton minusButton = new JButton("-");
minusButton.setPreferredSize(zoomBtnSize);
minusButton.addActionListener(e -> previewSketch.zoomMinus());
zoomOptions.add(minusButton);
JButton resetButton = new JButton("0");
resetButton.setPreferredSize(zoomBtnSize);
resetButton.addActionListener(e -> previewSketch.resetZoom());
zoomOptions.add(resetButton);
JButton plusButton = new JButton("+");
plusButton.setPreferredSize(zoomBtnSize);
plusButton.addActionListener(e -> previewSketch.zoomPlus());
zoomOptions.add(plusButton);
infobar.add(zoomOptions, BorderLayout.EAST);
this.add(infobar, BorderLayout.SOUTH);
previewController.refreshPreview();
previewSketch.resetZoom();
this.setSize(800, 600);
}
public void setDisplayedFilename(String filename) {
setTitle(baseWindowTitle + " - " + filename);
}
public static boolean isRetina() {
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice device = env.getDefaultScreenDevice();
try {
Field field = device.getClass().getDeclaredField("scale");
if (field != null) {
field.setAccessible(true);
Object scale = field.get(device);
if (scale instanceof Integer && (Integer) scale == 2) {
return true;
}
}
} catch (Exception ignore) {
}
return false;
}
@Override
public void mouseClicked(Node node, PreviewMouseEvent event, PreviewProperties properties, Workspace workspace) {
clickHandlers.stream().forEach(nodeClickHandler -> nodeClickHandler.accept(node, event));
}
public void addNodeClickHandler(NodeClickHandler handler) {
clickHandlers.add(handler);
}
@Override
public void setVisible(boolean b) {
super.setVisible(b);
previewController.refreshPreview();
previewSketch.refreshSketch();
}
/**
* Sets the displayed text of the infobar to text.
* After a certain time the text will be reset to the default text.
*
* @param text new infobar text
*/
public void setInfobarText(String text) {
SwingUtilities.invokeLater(() -> infobarText.setText(text));
// set text back to default text after 2 seconds
timer.schedule(new TimerTask() {
@Override
public void run() {
SwingUtilities.invokeLater(() -> infobarText.setText(defaultInfoBarText));
}
}, 2000);
}
}
package fucoin.gui.gephi;
import org.gephi.graph.api.Graph;
import org.gephi.preview.api.Item;
import org.gephi.preview.spi.ItemBuilder;
import org.openide.util.lookup.ServiceProvider;
@ServiceProvider(service = ItemBuilder.class)
public class ItemBuilderTemplate implements ItemBuilder {
@Override
public Item[] getItems(Graph graph) {
return new Item[0];
}
@Override
public String getType() {
return null;
}
}
package fucoin.gui.gephi;
import org.gephi.graph.api.Node;
import org.gephi.preview.api.Item;
public class LabelItem implements Item {
Node node;
public LabelItem(Node node) {
this.node = node;
}
@Override
public Object getSource() {
return node;
}
@Override
public String getType() {
return "label.sometype";
}
@Override
public <D> D getData(String key) {
return null;
}
@Override
public void setData(String key, Object value) {
}
@Override
public String[] getKeys() {
return new String[0];
}
}
package fucoin.gui.gephi;
import org.gephi.graph.api.GraphController;
import org.gephi.graph.api.Node;
import org.gephi.preview.api.PreviewMouseEvent;
import org.gephi.preview.api.PreviewProperties;
import org.gephi.preview.spi.PreviewMouseListener;
import org.gephi.project.api.Workspace;
import org.openide.util.Lookup;
import org.openide.util.lookup.ServiceProvider;
@ServiceProvider(service = PreviewMouseListener.class)
public class MouseListenerTemplate implements PreviewMouseListener {
@Override
public void mouseClicked(PreviewMouseEvent event, PreviewProperties properties, Workspace workspace) {
for (Node node : Lookup.getDefault().lookup(GraphController.class).getGraphModel(workspace).getGraph().getNodes()) {
if (clickingInNode(node, event)) {
if (properties.hasProperty("graphWindow.mouse.handler")) {
NodeMouseListener listener = properties.getValue("graphWindow.mouse.handler");
listener.mouseClicked(node, event, properties, workspace);
}
event.setConsumed(true);
return;
}
}
event.setConsumed(true);//So the renderer is executed and the graph repainted
}
@Override
public void mousePressed(PreviewMouseEvent event, PreviewProperties properties, Workspace workspace) {
}
@Override
public void mouseDragged(PreviewMouseEvent event, PreviewProperties properties, Workspace workspace) {
}
@Override
public void mouseReleased(PreviewMouseEvent event, PreviewProperties properties, Workspace workspace) {
}
private boolean clickingInNode(Node node, PreviewMouseEvent event) {
float xdiff = node.x() - event.x;
float ydiff = -node.y() - event.y;//Note that y axis is inverse for node coordinates
float radius = node.size();
return xdiff * xdiff + ydiff * ydiff < radius * radius;
}
}
package fucoin.gui.gephi;
import org.gephi.graph.api.Node;
import org.gephi.preview.api.PreviewMouseEvent;
import java.util.function.BiConsumer;
public interface NodeClickHandler extends BiConsumer<Node, PreviewMouseEvent> {
}
package fucoin.gui.gephi;
import org.gephi.graph.api.Node;
import org.gephi.preview.api.PreviewMouseEvent;
import org.gephi.preview.api.PreviewProperties;
import org.gephi.project.api.Workspace;
public interface NodeMouseListener {
public void mouseClicked(Node node, PreviewMouseEvent event, PreviewProperties properties, Workspace workspace);
}
package fucoin.gui.gephi;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import org.gephi.preview.api.G2DTarget;
import org.gephi.preview.api.PreviewController;
import org.gephi.preview.api.PreviewMouseEvent;
import org.gephi.preview.api.Vector;
import org.openide.util.Lookup;
public class PreviewSketch extends JPanel implements MouseListener, MouseWheelListener, MouseMotionListener {
private static final int WHEEL_TIMER = 500;
//Data
private final PreviewController previewController;
private final G2DTarget target;
//Geometry
private final Vector ref = new Vector();
private final Vector lastMove = new Vector();
//Utils
private final RefreshLoop refreshLoop = new RefreshLoop();
private Timer wheelTimer;
private boolean inited;
private final boolean isRetina;
public PreviewSketch(G2DTarget target, boolean retina) {
this.target = target;
previewController = Lookup.getDefault().lookup(PreviewController.class);
isRetina = retina;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (!inited) {
//Listeners
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
inited = true;
}
int width = (int) (getWidth() * (isRetina ? 2.0 : 1.0));
int height = (int) (getHeight() * (isRetina ? 2.0 : 1.0));
if (target.getWidth() != width || target.getHeight() != height) {
target.resize(width, height);
}
g.drawImage(target.getImage(), 0, 0, getWidth(), getHeight(), this);
}
public void refreshSketch() {
refreshLoop.refreshSketch();
}
public void setMoving(boolean moving) {
target.setMoving(moving);
}
@Override
public void mouseClicked(MouseEvent e) {
if (previewController.sendMouseEvent(buildPreviewMouseEvent(e, PreviewMouseEvent.Type.CLICKED))) {
refreshLoop.refreshSketch();
}
}
@Override
public void mousePressed(MouseEvent e) {
previewController.sendMouseEvent(buildPreviewMouseEvent(e, PreviewMouseEvent.Type.PRESSED));
ref.set(e.getX(), e.getY());
lastMove.set(target.getTranslate());
refreshLoop.refreshSketch();
}
@Override
public void mouseReleased(MouseEvent e) {
if (!previewController.sendMouseEvent(buildPreviewMouseEvent(e, PreviewMouseEvent.Type.RELEASED))) {
setMoving(false);
}
refreshLoop.refreshSketch();
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getUnitsToScroll() == 0) {
return;
}
float way = -e.getUnitsToScroll() / Math.abs(e.getUnitsToScroll());
target.setScaling(target.getScaling() * (way > 0 ? 2f : 0.5f));
setMoving(true);
if (wheelTimer != null) {
wheelTimer.cancel();
wheelTimer = null;
}
wheelTimer = new Timer();
wheelTimer.schedule(new TimerTask() {
@Override
public void run() {
setMoving(false);
refreshLoop.refreshSketch();
wheelTimer = null;
}
}, WHEEL_TIMER);
refreshLoop.refreshSketch();
}
@Override
public void mouseDragged(MouseEvent e) {
if (!previewController.sendMouseEvent(buildPreviewMouseEvent(e, PreviewMouseEvent.Type.DRAGGED))) {
setMoving(true);
Vector trans = target.getTranslate();
trans.set(e.getX(), e.getY());
trans.sub(ref);
trans.mult(isRetina ? 2f : 1f);
trans.div(target.getScaling()); // ensure const. moving speed whatever the zoom is
trans.add(lastMove);
refreshLoop.refreshSketch();
}
}
@Override
public void mouseMoved(MouseEvent e) {
}
public void zoomPlus() {
target.setScaling(target.getScaling() * 2f);
refreshLoop.refreshSketch();
}
public void zoomMinus() {
target.setScaling(target.getScaling() / 2f);
refreshLoop.refreshSketch();
}
public void resetZoom() {
target.reset();
refreshLoop.refreshSketch();
}
private Vector screenPositionToModelPosition(Vector screenPos) {
Vector center = new Vector(getWidth() / 2f, getHeight() / 2f);
Vector scaledCenter = Vector.mult(center, target.getScaling());
Vector scaledTrans = Vector.sub(center, scaledCenter);
Vector modelPos = new Vector(screenPos.x, screenPos.y);
modelPos.sub(scaledTrans);
modelPos.mult((isRetina) ? 2f : 1f);
modelPos.div(target.getScaling());
modelPos.sub(target.getTranslate());
return modelPos;
}
private PreviewMouseEvent buildPreviewMouseEvent(MouseEvent evt, PreviewMouseEvent.Type type) {
int mouseX = evt.getX();
int mouseY = evt.getY();
PreviewMouseEvent.Button button = PreviewMouseEvent.Button.LEFT;
if (SwingUtilities.isMiddleMouseButton(evt)) {
button = PreviewMouseEvent.Button.MIDDLE;
} else if (SwingUtilities.isLeftMouseButton(evt)) {
button = PreviewMouseEvent.Button.LEFT;
} else if (SwingUtilities.isRightMouseButton(evt)) {
button = PreviewMouseEvent.Button.RIGHT;
}
Vector pos = screenPositionToModelPosition(new Vector(mouseX, mouseY));
return new PreviewMouseEvent((int) pos.x, (int) pos.y, type, button, null);
}
private class RefreshLoop {
private final long DELAY = 100;
private final AtomicBoolean running = new AtomicBoolean();
private final AtomicBoolean refresh = new AtomicBoolean();
//Timer
private long timeout = DELAY * 10;
private Timer timer;
public RefreshLoop() {
super();
}
public void refreshSketch() {
refresh.set(true);
if (!running.getAndSet(true)) {
startTimer();
}
}
private void startTimer() {
timer = new Timer("PreviewRefreshLoop", true);
timer.schedule(new TimerTask() {
@Override
public void run() {
if (refresh.getAndSet(false)) {
target.refresh();
repaint();
} else if (timeout == 0) {
timeout = DELAY * 10;
stopTimer();
} else {
timeout -= DELAY;
}
}
}, 0, DELAY);
}
private void stopTimer() {
timer.cancel();
running.set(false);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment