/*
* Copyright 2011 Christian Thiemann <christian@spato.net>
* Developed at Northwestern University <http://rocs.northwestern.edu>
*
* This file is part of the SPaTo Visual Explorer (SPaTo).
*
* SPaTo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SPaTo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with SPaTo. If not, see <http://www.gnu.org/licenses/>.
*/
package net.spato.sve.app;
import java.awt.event.KeyEvent;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import net.spato.sve.app.data.*;
import net.spato.sve.app.layout.*;
import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PGraphics;
import processing.xml.XMLElement;
import de.cthiemann.tGUI.*;
public class SPaToGUI extends TransparentGUI {
protected SPaTo_Visual_Explorer app = null;
public TConsole console; // FIXME: should not be public
TTextField tfSearch;
TToggleButton btnRegexpSearch;
TChoiceWithRollover choiceNetwork;
TButton btnWorkspaceRecovery;
TChoice choiceMapProjection, choiceTomProjection, choiceTomScaling, choiceDistMat;
TChoice choiceDataset, choiceQuantity, choiceColormap;
TToggleButton btnColormapLog;
TToggleButton btnNodes, btnLinks, btnSkeleton, btnNeighbors, btnNetwork, btnLabels;
TToggleButton btnMap, btnTom;
NetworkDetailPanel networkDetail;
TLabel lblStatus;
// snapshot controls for node coloring quantity
TButton btnQuantityPrevSnapshot, btnQuantityNextSnapshot;
TSlider sldQuantitySnapshot;
// snapshot controls for album
TLabel lblAlbumName, lblAlbumSnapshot;
TButton btnAlbumPrevSnapshot, btnAlbumNextSnapshot;
TSlider sldAlbumSnapshot;
int fnsizeSmall = 10, fnsizeMedium = 12, fnsizeLarge = 14;
PFont fnSmall, fnMedium, fnLarge, fnLargeBold;
public boolean searchMatchesValid = false; // FIXME: public?
public boolean searchMatches[] = null; // this is true for nodes which are matched by the search phrase // FIXME: public?
int searchMatchesChild[] = null; // this is 1 for nodes which have any node in their branch that matches the search phrase
String searchMsg = null;
int searchUniqueMatch = -1;
public SPaToGUI(SPaTo_Visual_Explorer app) {
super(app, "/net/spato/SPaTo_Visual_Explorer/tGUI");
this.app = app;
fnSmall = createFont("GillSans", fnsizeSmall);
fnMedium = createFont("GillSans", fnsizeMedium);
fnLarge = createFont("GillSans", fnsizeLarge);
fnLargeBold = createFont("GillSans-Bold", fnsizeLarge);
// setup top panel
TPanel panel = createPanel(new TBorderLayout());
panel.setPadding(5);
choiceMapProjection = createChoice("projMap##");
choiceMapProjection.add(MapProjectionFactory.productNames);
choiceTomProjection = createChoice("projTom##");
choiceTomProjection.add(TomProjectionFactory.productNames);
choiceTomScaling = createChoice("scal##");
choiceTomScaling.add(ScalingFactory.productNames);
choiceDistMat = createChoice("distMat##", true);
choiceDistMat.setNoneString("Tree Depth");
choiceDistMat.setRenderer(new XMLElementRenderer(true));
panel.add(createCompactGroup(new TComponent[] {
createLabel("Projection:"), choiceMapProjection }), TBorderLayout.EAST);
panel.add(createCompactGroup(new TComponent[] {
createLabel("Projection:"), choiceTomProjection, choiceTomScaling, choiceDistMat }), TBorderLayout.EAST);
//
tfSearch = createTextField("search");
tfSearch.setEmptyText("Search");
btnRegexpSearch = createToggleButton("RegExp", app.prefs.getBoolean("search.regexp", false));
panel.add(createCompactGroup(new TComponent[] { tfSearch, btnRegexpSearch }), TBorderLayout.EAST);
//
btnNodes = createToggleButton("Nodes", true);
btnLinks = createToggleButton("Links", true);
btnSkeleton = createToggleButton("Skeleton", false);
btnNeighbors = createToggleButton("Neighbors", false); btnNeighbors.setVisible(false); // enabled but invis for now...
btnNetwork = createToggleButton("Network", false); btnNetwork.setVisible(false); // enabled but invis for now...
btnLabels = createToggleButton("Labels", true);
TPanel linksGroup = createCompactGroup(new TComponent[] { btnLinks, btnSkeleton, btnNeighbors, btnNetwork }, new TComponent.Spacing(0));
linksGroup.setBorderColor(app.color(200, 0, 0));
panel.add(createCompactGroup(new TComponent[] {
createLabel("Show:"), btnNodes, linksGroup, btnLabels }), TBorderLayout.WEST);
//
lblAlbumName = createLabel("Album:");
btnAlbumPrevSnapshot = createButton("<"); btnAlbumPrevSnapshot.setActionCommand("snapshot##album##prev");
btnAlbumNextSnapshot = createButton(">"); btnAlbumNextSnapshot.setActionCommand("snapshot##album##next");
sldAlbumSnapshot = createSlider("snapshot##album");
lblAlbumSnapshot = createLabel("Snapshot");
panel.add(createCompactGroup(new TComponent[] {
lblAlbumName, btnAlbumPrevSnapshot, sldAlbumSnapshot, btnAlbumNextSnapshot, lblAlbumSnapshot }), TBorderLayout.WEST);
//
panel.add(createLabel(""), TBorderLayout.WEST); // place-holder to keep choiceNetwork in place
//
add(panel, TBorderLayout.NORTH);
// setup network and map/tom switcher
panel = createPanel(new TBorderLayout());
panel.setPadding(5, 10);
choiceNetwork = new TChoiceWithRollover(gui, "network##"); choiceNetwork.setFont(fnLargeBold);
choiceNetwork.setEmptyString("right-click here");
choiceNetwork.setContextMenu(createPopupMenu(new String[][] {
// { "New document\u2026", "document##new" },
{ "Open document\u2026", "document##open" },
{ "Save document", "document##save" },
{ "Save document as\u2026", "document##saveAs" },
{ "Save uncompressed", "document##compressed" },
{ "Close document", "document##close" },
null,
{ "Open workspace\u2026", "workspace##open" },
{ "Save workspace", "workspace##save" },
{ "Save workspace as\u2026", "workspace##saveAs" },
null,
{ "Take screenshot\u2026", "screenshot" },
null,
{ "Check for updates", "update" }
}));
panel.add(createCompactGroup(new TComponent[] { choiceNetwork }, 5), TBorderLayout.WEST);
btnMap = createToggleButton("Map"); btnMap.setFont(fnLargeBold);
btnTom = createToggleButton("Tom"); btnTom.setFont(fnLargeBold);
new TButtonGroup(new TToggleButton[] { btnMap, btnTom });
panel.add(createCompactGroup(new TComponent[] { btnMap, btnTom }, 5), TBorderLayout.EAST);
add(panel, TBorderLayout.NORTH);
// setup workspace recovery button
XMLElement xmlWorkspace = XMLElement.parse(app.prefs.get("workspace", "<workspace />"));
if ((xmlWorkspace != null) && (xmlWorkspace.getChildren("document").length > 0)) {
btnWorkspaceRecovery = createButton("Recover previous workspace");
btnWorkspaceRecovery.setActionCommand("workspace##recover");
String tooltip = "Attempts to reload all documents that were open\n" +
"when the application was closed the last time:\n";
XMLElement xmlDocument[] = xmlWorkspace.getChildren("document");
for (int i = 0; i < xmlDocument.length; i++) {
tooltip += " " + (i+1) + ". " + xmlDocument[i].getString("src") + "\n";
// File f = new File(xmlDocument[i].getString("src"));
// tooltip += (i+1) + ". ";
// tooltip += "[color=127,127,127]" + f.getParent() + File.separator + "[/color]";
// String name = f.getName();
// String ext = null;
// if (name.endsWith(".spato")) { name = name.substring(0, name.length() - 7); ext = ".spato"; }
// tooltip += name;
// if (ext != null) tooltip += "[color=127,127,127]" + ext + "[/color]";
// tooltip += "\n";
}
btnWorkspaceRecovery.setToolTip(tooltip, TToolTip.INFO);
panel = createPanel(new TBorderLayout());
panel.setPadding(5, 10);
panel.add(createCompactGroup(new TComponent[] { btnWorkspaceRecovery }, 2), TBorderLayout.WEST);
add(panel, TBorderLayout.NORTH);
}
// setup bottom panel
panel = createPanel(new TBorderLayout());
panel.setPadding(5);
choiceDataset = createChoice("dataset##", true);
choiceDataset.setEmptyString("\u2014 right-click to create new dataset \u2014");
choiceDataset.setRenderer(new XMLElementRenderer());
// choiceDataset.setContextMenu(createPopupMenu(new String[][] {
// { "New", "dataset####new" },
// { "Rename", "dataset####rename" },
// { "Delete", "dataset####delete" }
// }));
choiceQuantity = createChoice("quantity##", true);
choiceQuantity.setRenderer(new XMLElementRenderer());
// choiceQuantity.setContextMenu(createPopupMenu(new String[][] {
// { "Import\u2026", "quantity####import" },
// { "Rename", "quantity####rename" },
// { "Delete", "quantity####delete" }
// }));
choiceColormap = createChoice("colormap##");
choiceColormap.setEmptyString("\u2014 no quantity selected \u2014");
btnColormapLog = createToggleButton("log");
btnQuantityPrevSnapshot = createButton("<"); btnQuantityPrevSnapshot.setActionCommand("snapshot##quantity##prev");
btnQuantityNextSnapshot = createButton(">"); btnQuantityNextSnapshot.setActionCommand("snapshot##quantity##next");
sldQuantitySnapshot = createSlider("snapshot##quantity");
// FIXME: snapshot controls still wiggle due to stupid XMLElementRenderer
panel.add(createCompactGroup(new TComponent[] {
createLabel("Node Coloring:"), choiceDataset, createLabel("/"), choiceQuantity,
createCompactGroup(new TComponent[] { btnQuantityPrevSnapshot, sldQuantitySnapshot, btnQuantityNextSnapshot }),
createLabel("/"), choiceColormap, btnColormapLog }), TBorderLayout.WEST);
add(panel, TBorderLayout.SOUTH);
// node coloring quantity snapshot controls
// btnQuantityPrevSnapshot = createButton("<"); btnQuantityPrevSnapshot.setActionCommand("snapshot##quantity##prev");
// btnQuantityNextSnapshot = createButton(">"); btnQuantityNextSnapshot.setActionCommand("snapshot##quantity##next");
// sldQuantitySnapshot = createSlider("snapshot##quantity");
// panel.add(createCompactGroup(new TComponent[] { btnQuantityPrevSnapshot, sldQuantitySnapshot, btnQuantityNextSnapshot }), TBorderLayout.WEST);
// TWindow win = new TWindow(gui, new TCompactGroupLayout(0));
// win.setMargin(1, 3);
// win.add(btnQuantityPrevSnapshot);
// win.add(sldQuantitySnapshot);
// win.add(btnQuantityNextSnapshot);
// win.setVisible(false);
// add(win);
// status bar
lblStatus = createLabel("");
lblStatus.setAlignment(TLabel.ALIGN_RIGHT);
lblStatus.setFont(createFont("GillSans-Bold", 10));
panel.add(lblStatus, TBorderLayout.CENTER);
// network details panel
panel = createPanel(new TBorderLayout());
panel.setPadding(0, 10);
networkDetail = new NetworkDetailPanel(gui);
networkDetail.setVisible(false);
choiceNetwork.rollOverComponent = networkDetail;
panel.add(networkDetail, TBorderLayout.NORTH);
add(panel, TBorderLayout.WEST);
// setup console
console = createConsole(SPaTo_Visual_Explorer.VERSION_DEBUG.equals("alpha"));
int tE = 5000;
console.logInfo("SPaTo Visual Explorer").tE = tE;
console.logNote("Version " + SPaTo_Visual_Explorer.VERSION +
((SPaTo_Visual_Explorer.VERSION_DEBUG.length() > 0) ? " " + SPaTo_Visual_Explorer.VERSION_DEBUG : "") +
" (" + SPaTo_Visual_Explorer.VERSION_DATE + ")").tE = tE;
if (SPaTo_Visual_Explorer.VERSION_DEBUG.equals("alpha"))
console.logError("This is an alpha version \u2013 don't use it unless you know what you are doing").tE = tE;
else if (SPaTo_Visual_Explorer.VERSION_DEBUG.equals("beta"))
console.logWarning("This is a beta version \u2013 expect unexpected behavior").tE = tE;
console.logNote("Copyright (C) 2008\u20132011 by Christian Thiemann").tE = tE;
console.logNote("Research on Complex Systems, Northwestern University").tE = tE;
console.logDebug("--------------------------------------------------------");
console.logDebug("[OS] " + System.getProperty("os.name") + " " + System.getProperty("os.version") + " (" + System.getProperty("os.arch") + ") " + System.getProperty("sun.arch.data.model"));
console.logDebug("[JRE] " + System.getProperty("java.runtime.name") + " " + System.getProperty("java.runtime.version"));
console.logDebug("[JVM] " + System.getProperty("java.vm.name") + " " + System.getProperty("java.vm.version") + " (" + System.getProperty("java.vm.vendor") + ")");
console.logDebug("[path] " + System.getenv(((PApplet.platform != PApplet.WINDOWS) ? ((PApplet.platform == PApplet.MACOSX) ? "DY" : "") + "LD_LIBRARY_" : "") + "PATH"));
console.logDebug("[mem] max: " + (Runtime.getRuntime().maxMemory()/1024/1024) + " MB");
// if (!JNMatLib.isLoaded() && (SPaTo_Visual_Explorer.VERSION_DEBUG.length() > 0)) console.logError("[JNMatLib] " + JNMatLib.getError().getMessage());
console.logDebug("--------------------------------------------------------");
add(console);
// setup hotkeys and drop target
setupHotkeys();
app.dataTransferHandler = new DataTransferHandler(app);
}
private void setupHotkeys() {
tfSearch.setHotKey(KeyEvent.VK_F, PApplet.MENU_SHORTCUT);
if (btnWorkspaceRecovery != null) btnWorkspaceRecovery.setHotKey(KeyEvent.VK_R, PApplet.MENU_SHORTCUT);
else btnRegexpSearch.setHotKey(KeyEvent.VK_R, PApplet.MENU_SHORTCUT);
btnNodes.setHotKey(KeyEvent.VK_N);
btnLinks.setHotKey(KeyEvent.VK_L);
btnSkeleton.setHotKey(KeyEvent.VK_B);
btnNeighbors.setHotKey(KeyEvent.VK_E);
btnNetwork.setHotKey(KeyEvent.VK_F);
btnLabels.setHotKey(KeyEvent.VK_L, KeyEvent.SHIFT_MASK);
//
btnMap.setHotKey(KeyEvent.VK_V);
btnTom.setHotKey(KeyEvent.VK_V);
choiceDistMat.setHotKeyChar('d');
choiceNetwork.setHotKeyChar('~');
choiceDataset.setHotKeyChar(PApplet.TAB);
choiceDataset.setShortcutChars(new char[] { 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p' });
choiceQuantity.setHotKeyChar('`');
choiceQuantity.setShortcutChars(new char[] { '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' });
choiceColormap.setHotKeyChar('c');
btnColormapLog.setHotKey(KeyEvent.VK_C, KeyEvent.SHIFT_MASK);
btnQuantityPrevSnapshot.setHotKey(KeyEvent.VK_COMMA);
btnQuantityNextSnapshot.setHotKey(KeyEvent.VK_PERIOD);
}
public void update() { update(false); }
public void update(boolean fast) {
SPaToDocument doc = app.doc;
if (!fast) updateWorkspace();
// update visibility of components
btnNodes.getParent().setVisibleAndEnabled(doc != null);
tfSearch.getParent().setVisibleAndEnabled(doc != null);
tfSearch.setEmptyText("Search" + (btnRegexpSearch.isSelected() ? " (RegExp)" : ""));
btnMap.getParent().setVisibleAndEnabled(doc != null);
choiceMapProjection.getParent().setVisibleAndEnabled((doc != null) && (doc.view.viewMode == SPaToView.VIEW_MAP));
choiceTomProjection.getParent().setVisibleAndEnabled((doc != null) && (doc.view.viewMode == SPaToView.VIEW_TOM));
choiceDataset.getParent().setVisibleAndEnabled(doc != null);
btnNodes.getParent().setVisibleAndEnabled(doc != null);
sldAlbumSnapshot.getParent().setVisibleAndEnabled((doc != null) && (doc.getAlbum() != null));
lblStatus.setText(((doc == null) || (doc.view.ih == -1)) ? "" : doc.view.nodes[doc.view.ih].name);
app.platformMagic.update();
if (doc == null) return;
choiceMapProjection.setEnabled(doc.view.hasMapLayout);
choiceTomProjection.setEnabled(doc.view.hasTomLayout);
choiceTomScaling.setEnabled(doc.view.hasTomLayout);
choiceDistMat.setEnabled(doc.view.hasTomLayout);
btnMap.setEnabled(doc.view.hasMapLayout);
btnTom.setEnabled(doc.view.hasTomLayout);
switch (doc.view.viewMode) {
case SPaToView.VIEW_MAP: btnMap.getButtonGroup().setSelected(btnMap); break;
case SPaToView.VIEW_TOM: btnTom.getButtonGroup().setSelected(btnTom); break;
}
btnNodes.setSelected(doc.view.showNodes);
btnLinks.setSelected((doc.view.showLinks && !doc.view.showSkeleton && !doc.view.showNeighbors && !doc.view.showNetwork) ||
(doc.view.showSkeleton && doc.view.showLinksWithSkeleton) ||
(doc.view.showNeighbors && doc.view.showLinksWithNeighbors) ||
(doc.view.showNetwork && doc.view.showLinksWithSkeleton));
btnLinks.getParent().setBorder(doc.view.showSkeleton || doc.view.showNeighbors || doc.view.showNetwork ? 1 : 0);
btnSkeleton.setSelected(doc.view.showSkeleton);
btnNeighbors.setSelected(doc.view.showNeighbors);
btnNetwork.setSelected(doc.view.showNetwork);
btnLabels.setSelected(doc.view.showLabels);
if (app.frameRate < 15) SPaToView.fastNodes = true;
if (app.frameRate > 30) SPaToView.fastNodes = false;
if (app.VERSION_DEBUG.equals("beta")) {
if (app.frameRate < 14) console.setShowDebug(true);
if (app.frameRate > 20) console.setShowDebug(false);
}
// update search matches if necessary
if (!searchMatchesValid)
updateSearchMatches();
// thorough updates
if (!fast) {
updateProjection();
updateNodeColoring();
updateAlbumControls();
}
}
public void updateWorkspace() {
choiceNetwork.removeAll();
if (app.workspace.docs.size() > 0) {
choiceNetwork.add(app.workspace.docs.toArray());
if (app.workspace.doc != null)
choiceNetwork.select(app.workspace.docs.indexOf(app.workspace.doc));
}
choiceNetwork.getContextMenu().setEnabled("document##save", app.workspace.doc != null);
choiceNetwork.getContextMenu().setEnabled("document##saveAs", app.workspace.doc != null);
choiceNetwork.getContextMenu().setEnabled("document##compressed", app.workspace.doc != null);
choiceNetwork.getContextMenu().getItem("document##compressed").setText(
((app.workspace.doc != null) && app.workspace.doc.compressed) ? "Save uncompressed" : "Save compressed");
choiceNetwork.getContextMenu().setEnabled("document##close", app.workspace.doc != null);
choiceNetwork.getContextMenu().setEnabled("workspace##save", app.workspace.docs.size() > 0);
choiceNetwork.getContextMenu().setEnabled("workspace##saveAs", app.workspace.docs.size() > 0);
if ((btnWorkspaceRecovery != null) && !app.workspace.showWorkspaceRecoveryButton) {
remove(btnWorkspaceRecovery.getParent().getParent()); // remove the button from the GUI
btnWorkspaceRecovery.setHotKey(0); // release hotkey
btnWorkspaceRecovery = null; // ... and we don't need that anymore
btnRegexpSearch.setHotKey(KeyEvent.VK_R, PApplet.MENU_SHORTCUT); // re-bind hotkey to Regexp toggle
}
}
public void updateProjection() {
if (app.workspace.doc == null) return;
if (app.workspace.doc.view.hasMapLayout)
choiceMapProjection.select(app.workspace.doc.view.xmlProjection.getString("name"));
if (app.workspace.doc.view.hasTomLayout) {
choiceTomProjection.select(app.workspace.doc.view.layouts[app.workspace.doc.view.l].projection);
choiceTomScaling.select(app.workspace.doc.view.layouts[app.workspace.doc.view.l].scaling);
choiceDistMat.removeAll();
choiceDistMat.add(app.workspace.doc.getDistanceQuantities());
choiceDistMat.select(app.workspace.doc.view.xmlDistMat);
}
}
public void updateNodeColoring() {
SPaToDocument doc = app.workspace.doc;
if (doc == null) { choiceDataset.getParent().setVisibleAndEnabled(false); return; }
choiceDataset.getParent().setVisibleAndEnabled(true);
//
choiceDataset.removeAll();
choiceDataset.add(doc.getDatasets());
choiceDataset.select(doc.getSelectedDataset());
// choiceDataset.getContextMenu().setEnabled("dataset####rename", choiceDataset.getSelectedItem() != null);
// choiceDataset.getContextMenu().setEnabled("dataset####delete", choiceDataset.getSelectedItem() != null);
//
choiceQuantity.removeAll();
if (doc.getSelectedDataset() != null) {
choiceQuantity.add(doc.getQuantities());
choiceQuantity.select(doc.getSelectedQuantity());
}
// choiceQuantity.getContextMenu().setEnabled("quantity####import", choiceDataset.getSelectedItem() != null);
// choiceQuantity.getContextMenu().setEnabled("quantity####rename", choiceQuantity.getSelectedItem() != null);
// choiceQuantity.getContextMenu().setEnabled("quantity####delete", choiceQuantity.getSelectedItem() != null);
choiceQuantity.setEmptyString((doc.getSelectedDataset() == null)
? "\u2014 no dataset selected \u2014" : "\u2014 right-click to import data \u2014");
//
choiceColormap.removeAll();
if (doc.view.hasData) {
choiceColormap.setRenderer(doc.view.colormap.new Renderer());
choiceColormap.add(doc.view.colormap.colormaps);
choiceColormap.select(doc.view.colormap.getColormapName());
}
//
btnColormapLog.setVisibleAndEnabled(doc.view.hasData && !doc.view.colormap.getColormapName().equals("discrete"));
if (doc.view.hasData) btnColormapLog.setSelected(doc.view.colormap.isLogscale());
//
// TWindow win = (TWindow)sldQuantitySnapshot.getParent();
TPanel win = (TPanel)sldQuantitySnapshot.getParent();
win.setVisibleAndEnabled(false);
XMLElement series = doc.getSelectedSnapshotSeriesContainer(doc.getSelectedQuantity());
if (series != null) {
XMLElement snapshots[] = series.getChildren("snapshot");
sldQuantitySnapshot.setValueBounds(0, snapshots.length-1);
sldQuantitySnapshot.setValue(doc.getSelectedSnapshotIndex(series));
sldQuantitySnapshot.setPreferredWidth(PApplet.max(75, PApplet.min(app.width-50, snapshots.length-1)));
win.setVisibleAndEnabled(true);
TComponent.Dimension d = win.getPreferredSize();
validate();
TComponent.Point p = choiceQuantity.getLocationOnScreen();
float x = PApplet.max(3, p.x - btnQuantityPrevSnapshot.getPreferredSize().width/2);
x = PApplet.min(app.width - 3 - win.getWidth(), x);
float y = p.y - d.height; // FIXME: what's all this stuff here?
// win.setBounds(x, y, d.width, d.height);
}
}
public void updateAlbumControls() {
SPaToDocument doc = app.workspace.doc;
if (doc == null) return; // done
XMLElement xmlAlbum = doc.getAlbum();
if (xmlAlbum == null) return; // done
lblAlbumName.setText(xmlAlbum.getString("name", xmlAlbum.getString("id", "Unnamed Album")) + ":");
XMLElement snapshots[] = xmlAlbum.getChildren("snapshot");
sldAlbumSnapshot.setValueBounds(0, snapshots.length-1);
sldAlbumSnapshot.setValue(doc.getSelectedSnapshotIndex(xmlAlbum));
sldAlbumSnapshot.setPreferredWidth(PApplet.max(100, PApplet.min(app.width/2, snapshots.length-1)));
XMLElement snapshot = doc.getSelectedSnapshot(xmlAlbum);
lblAlbumSnapshot.setText("[" + snapshot.getString("label", snapshot.getString("id", "Unnamed Snapshot")) + "]");
}
public void updateSearchMatches() {
SPaToDocument doc = app.workspace.doc;
searchMsg = null;
searchUniqueMatch = -1;
if ((doc == null) || !doc.view.hasNodes) return;
String searchPhrase = tfSearch.getText();
if (searchPhrase.length() == 0) {
searchMatches = null;
return;
}
if (searchMatches == null) {
searchMatches = new boolean[doc.view.NN];
searchMatchesChild = new int[doc.view.NN];
}
if (btnRegexpSearch.isSelected()) {
try {
Pattern p = Pattern.compile(searchPhrase);
if (p.matcher("").find()) throw new PatternSyntaxException("Expression matches empty string", searchPhrase, -1);
for (int i = 0; i < doc.view.NN; i++)
searchMatches[i] = p.matcher(doc.view.nodes[i].label).find() || p.matcher(doc.view.nodes[i].name).find();
} catch (PatternSyntaxException e) {
searchMsg = "E" + e.getDescription();
if (e.getIndex() > -1)
searchMsg += ": " + e.getPattern().substring(0, e.getIndex());
}
} else {
boolean caseSensitive = !searchPhrase.equals(searchPhrase.toLowerCase()); // ignore case if no upper-case letter present
for (int i = 0; i < doc.view.NN; i++)
searchMatches[i] =
(caseSensitive
? doc.view.nodes[i].label.contains(searchPhrase)
: doc.view.nodes[i].label.toLowerCase().contains(searchPhrase))
||
(caseSensitive
? doc.view.nodes[i].name.contains(searchPhrase)
: doc.view.nodes[i].name.toLowerCase().contains(searchPhrase));
}
searchMatchesValid = true;
for (int i = 0; i < doc.view.NN; i++)
if (searchMatches[i])
if (searchUniqueMatch == -1) searchUniqueMatch = i;
else { searchUniqueMatch = -1; break; }
if (searchUniqueMatch > -1)
searchMsg = "M" + doc.view.nodes[searchUniqueMatch].name;
}
public void actionPerformed(String cmd) {
SPaToDocument doc = app.workspace.doc;
String argv[] = PApplet.split(cmd, "##");
if (argv[0].equals("workspace")) {
if (argv[1].equals("open")) app.workspace.openWorkspace();
if (argv[1].equals("save")) app.workspace.saveWorkspace();
if (argv[1].equals("saveAs")) app.workspace.saveWorkspace(true);
if (argv[1].equals("recover")) app.workspace.replaceWorkspace(XMLElement.parse(app.prefs.get("workspace", "<workspace />")));
} else if (argv[0].equals("document")) {
if (argv[1].equals("new")) app.workspace.newDocument();
if (argv[1].equals("open")) app.workspace.openDocument();
if (argv[1].equals("save")) app.workspace.saveDocument();
if (argv[1].equals("saveAs")) app.workspace.saveDocument(true);
if (argv[1].equals("compressed")) { doc.setCompressed(!doc.isCompressed()); app.workspace.saveDocument(); }
if (argv[1].equals("close")) app.workspace.closeDocument();
} else if (argv[0].equals("search")) {
searchMatchesValid = false;
if (argv[1].equals("enterKeyPressed") && (searchUniqueMatch > -1)) {
doc.view.setRootNode(searchUniqueMatch);
tfSearch.setText("");
}
} else if (argv[0].equals("RegExp")) {
searchMatchesValid = false;
app.prefs.putBoolean("search.regexp", btnRegexpSearch.isSelected());
} else if (argv[0].equals("network"))
app.workspace.switchToNetwork(argv[1]);
else if (argv[0].equals("projMap"))
doc.view.setMapProjection(argv[1]);
else if (argv[0].equals("projTom")) {
doc.view.layouts[0].setupProjection(argv[1]);
if (doc.view.r > -1) doc.view.layouts[0].updateProjection(doc.view.r, doc.view.D);
} else if (argv[0].equals("distMat"))
doc.setDistanceQuantity((XMLElement)choiceDistMat.getSelectedItem());
else if (argv[0].equals("scal")) {
doc.view.layouts[0].setupScaling(argv[1], doc.view.minD/1.25f); // FIXME: when does this insanity end?
if (doc.view.r > -1) doc.view.layouts[0].updateProjection(doc.view.r, doc.view.D);
if (doc.view.xmlDistMat != null) doc.view.xmlDistMat.setString("scaling", argv[1]);
} else if (argv[0].equals("Map"))
doc.view.viewMode = SPaToView.VIEW_MAP;
else if (argv[0].equals("Tom"))
doc.view.viewMode = SPaToView.VIEW_TOM;
else if (argv[0].equals("Nodes"))
doc.view.showNodes = !doc.view.showNodes;
else if (argv[0].equals("Links")) {
if (doc.view.showSkeleton) doc.view.showLinksWithSkeleton = !doc.view.showLinksWithSkeleton;
else if (doc.view.showNeighbors) doc.view.showLinksWithNeighbors = !doc.view.showLinksWithNeighbors;
else if (doc.view.showNetwork) doc.view.showLinksWithNetwork = !doc.view.showLinksWithNetwork;
else doc.view.showLinks = !doc.view.showLinks;
} else if (argv[0].equals("Skeleton")) {
doc.view.showSkeleton = !doc.view.showSkeleton;
if (doc.view.showSkeleton && doc.view.showNetwork) doc.view.showNetwork = false;
if (doc.view.showSkeleton && doc.view.showNeighbors) doc.view.showNeighbors = false;
} else if (argv[0].equals("Neighbors")) {
if (doc.view.hasLinks) {
doc.view.showNeighbors = !doc.view.showNeighbors;
if (doc.view.showNeighbors && doc.view.showSkeleton) doc.view.showSkeleton = false;
if (doc.view.showNeighbors && doc.view.showNetwork) doc.view.showNetwork = false;
} else
console.logWarning("Network links not available in data file");
} else if (argv[0].equals("Network")) {
if (doc.view.hasLinks) {
doc.view.showNetwork = !doc.view.showNetwork;
if (doc.view.showNetwork && doc.view.showSkeleton) doc.view.showSkeleton = false;
if (doc.view.showNetwork && doc.view.showNeighbors) doc.view.showNeighbors = false;
} else
console.logWarning("Full network not available in data file");
} else if (argv[0].equals("Labels"))
doc.view.showLabels = !doc.view.showLabels;
else if (argv[0].equals("dataset")) {
if (!argv[1].equals(""))
doc.setSelectedDataset((XMLElement)choiceDataset.getSelectedItem());
else if (argv[2].equals("new")) {
XMLElement xmlDataset = new XMLElement("dataset");
xmlDataset.setString("name", "New Dataset");
doc.addDataset(xmlDataset);
doc.setSelectedDataset(xmlDataset);
updateNodeColoring();
actionPerformed("dataset####rename");
} else if (argv[2].equals("rename"))
new InPlaceRenamingTextField(gui, choiceDataset).show();
else if (argv[2].equals("delete"))
doc.removeDataset((XMLElement)choiceDataset.getSelectedItem());
} else if (argv[0].equals("quantity")) {
if (!argv[1].equals(""))
doc.setSelectedQuantity((XMLElement)choiceQuantity.getSelectedItem());
// else if (argv[2].equals("import"))
// new QuantityImportWizard(doc).start();
else if (argv[2].equals("rename"))
new InPlaceRenamingTextField(gui, choiceQuantity).show();
else if (argv[2].equals("delete"))
doc.removeQuantity((XMLElement)choiceQuantity.getSelectedItem());
} else if (argv[0].equals("colormap"))
doc.view.colormap.setColormap(argv[1]);
else if (argv[0].equals("log"))
doc.view.colormap.setLogscale(btnColormapLog.isSelected());
else if (argv[0].equals("snapshot")) {
TSlider source = null; XMLElement target = null;
if (argv[1].equals("album")) { source = sldAlbumSnapshot; target = doc.getAlbum(); }
else if (argv[1].equals("quantity")) { source = sldQuantitySnapshot; target = doc.getSelectedQuantity(); }
else return;
if (argv[2].equals("valueChanged"))
doc.setSelectedSnapshot(target, source.getValue());
else if (argv[2].equals("next"))
doc.setSelectedSnapshot(target, +1, true);
else if (argv[2].equals("prev"))
doc.setSelectedSnapshot(target, -1, true);
} else if (argv[0].equals("screenshot"))
new Screenshot(app).showSettingsWindow();
else if (argv[0].equals("update"))
app.checkForUpdates(true);
update();
}
public void draw(PGraphics g) {
super.draw(g);
if (searchMsg != null) {
g.textFont(fnMedium); g.noStroke();
g.fill(searchMsg.startsWith("E") ? 255 : 0, 0, 0);
Point p = tfSearch.getLocationOnScreen();
g.text(searchMsg.substring(1), p.x, p.y + tfSearch.getHeight() + 15);
}
}
class XMLElementRenderer extends TChoice.StringRenderer {
boolean includeDataset = false;
XMLElementRenderer() { this(false); }
XMLElementRenderer(boolean includeDataset) { this.includeDataset = includeDataset; }
public String getActionCommand(Object o) {
XMLElement xml = (XMLElement)o;
String str = xml.getString("id");
if (includeDataset && xml.getName().equals("data") && (xml.getParent() != null))
str = xml.getParent().getString("id") + "##" + str;
return str;
}
public String getSnapshotLabel(XMLElement xml) {
String res = xml.getString("label");
if (res == null) try { // FIXME: using app.doc here is sketchy...
res = app.doc.getChild(app.doc.getChild("album[@id=" + xml.getString("album") + "]"), "snapshot[@selected]").getString("label");
} catch (Exception e) { /* ignore any NullPointerExceptions or other stuff */ }
return res;
}
// FIXME: maybe the string should be cached? (and erased on invalidate())
public String getString(Object o, boolean inMenu) {
XMLElement xml = (XMLElement)o;
String str = xml.getString("name", xml.getString("id"));
if (includeDataset && inMenu && xml.getName().equals("data") && (xml.getParent() != null))
str = xml.getParent().getString("name", xml.getParent().getString("id")) + ": " + str;
XMLElement snapshot = app.workspace.doc.getSelectedSnapshot(xml);
if (!inMenu && (snapshot != null)) {
String strsnap = "";
while (snapshot != xml) {
strsnap = strsnap + " [" + getSnapshotLabel(snapshot) + "]";
snapshot = snapshot.getParent();
}
str += strsnap;
}
return str;
}
}
class TChoiceWithRollover extends TChoice {
class SPaToDocumentRenderer extends StringRenderer {
public boolean getEnabled(Object o) { return ((SPaToDocument)o).view.hasMapLayout || ((SPaToDocument)o).view.hasTomLayout; }
public String getActionCommand(Object o) { return ((SPaToDocument)o).getName(); }
public TComponent.Dimension getPreferredSize(TChoice c, Object o, boolean inMenu) {
SPaToDocument doc = (SPaToDocument)o;
TComponent.Dimension d = super.getPreferredSize(c, doc.getName(), inMenu);
if (inMenu) {
app.textFont(style.getFont()); // FIXME: obtain PGraphics context
String desc = !getEnabled(o) ? " (loading...)" : " \u2013 " + doc.getTitle();
d.width += 5 + app.textWidth(desc);
}
return d;
}
public void draw(TChoice c, PGraphics g, Object o, TComponent.Rectangle bounds, boolean inMenu) {
SPaToDocument doc = (SPaToDocument)o;
String name = doc.getName();
g.noStroke();
g.textFont(c.getFont());
g.fill(getEnabled(o) ? c.getForeground() : g.color(127));
g.textAlign(g.LEFT, g.BASELINE);
float x = bounds.x;
float y = bounds.y + bounds.height - g.textDescent();
float h = g.textAscent() + g.textDescent();
if (bounds.height > h) y -= (bounds.height - h)/2;
g.text(name, x, y);
if (inMenu) {
x += g.textWidth(name) + 5;
g.textFont(style.getFont());
String desc = !getEnabled(o) ? " (loading...)" : " \u2013 " + doc.getTitle();
g.text(desc, x, y);
}
}
}
TComponent rollOverComponent = null;
TChoiceWithRollover(TransparentGUI gui, String actionCmdPrefix) {
super(gui, actionCmdPrefix); setRenderer(new SPaToDocumentRenderer()); }
public void handleMouseEntered() { super.handleMouseEntered(); if (rollOverComponent != null) rollOverComponent.setVisible(true); }
public void handleMouseExited() { super.handleMouseExited(); if (rollOverComponent != null) rollOverComponent.setVisible(false); }
}
class NetworkDetailPanel extends TComponent {
NetworkDetailPanel(TransparentGUI gui) { super(gui); setPadding(5, 10); setMargin(0);
setBackgroundColor(style.getBackgroundColorForCompactGroups()); }
public TComponent.Dimension getMinimumSize() {
SPaToDocument doc = app.workspace.doc;
if ((doc == null) || !doc.view.hasNodes) return new TComponent.Dimension(0, 0);
app.textFont(fnLarge); // FIXME: we should have a PGraphics context here...
float width = app.textWidth(doc.getTitle()), height = app.textAscent() + 1.5f*app.textDescent();
app.textFont(fnMedium);
String networkMeta[] = PApplet.split(doc.getDescription(), '\n');
for (int i = 0; i < networkMeta.length; i++) {
width = PApplet.max(width, app.textWidth(networkMeta[i]));
height += app.textAscent() + 1.5f*app.textDescent();
}
return new TComponent.Dimension(width, height);
}
public void draw(PGraphics g) {
SPaToDocument doc = app.workspace.doc;
if ((doc == null) || !doc.view.hasNodes) return;
super.draw(g);
float x = bounds.x + padding.left, y = bounds.y + padding.top;
g.textAlign(g.LEFT, g.BASELINE);
g.textFont(fnLarge);
g.noStroke();
g.fill(0);
y += g.textAscent() + .5f*g.textDescent();
g.text(doc.getTitle(), x, y);
y += g.textDescent();
g.fill(127);
g.textFont(fnMedium);
String networkMeta[] = PApplet.split(doc.getDescription(), '\n');
for (int i = 0; i < networkMeta.length; i++) {
y += g.textAscent() + .5f*g.textDescent();
g.text(networkMeta[i], x, y);
y += g.textDescent();
}
}
}
class InPlaceRenamingTextField extends TTextField {
TChoice choice = null;
XMLElement xml = null;
InPlaceRenamingTextField(TransparentGUI gui, TChoice choice) { super(gui); this.choice = choice; }
public TComponent.Dimension getPreferredSize() {
return new TComponent.Dimension(PApplet.max(choice.getWidth(), 50), choice.getHeight()); }
public void show() {
xml = (XMLElement)choice.getSelectedItem();
if (xml == null) return;
setText(xml.getString("name"));
setSelection(0, text.length());
TContainer parent = choice.getParent();
for (int i = 0; i < parent.getComponentCount(); i++)
if (parent.getComponent(i) == choice)
{ parent.add(this, choice.getLayoutHint(), i); break; }
choice.setVisibleAndEnabled(false);
parent.validate();
gui.requestFocus(this);
}
public void draw(PGraphics g) {
if (isFocusOwner())
super.draw(g);
else {
if (text.length() > 0)
xml.setString("name", text);
choice.setVisibleAndEnabled(true);
getParent().remove(this);
}
}
}
}