001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.ui.spriteManager; 033 034import java.util.ArrayList; 035import java.util.Collection; 036import java.util.HashMap; 037import java.util.Iterator; 038import java.util.Map; 039 040import org.graphstream.graph.Element; 041import org.graphstream.ui.graphicGraph.stylesheet.Style; 042import org.graphstream.ui.graphicGraph.stylesheet.Values; 043 044/** 045 * A gentle little sprite. 046 * 047 * <p> 048 * Sprite objects allow to add data representations in a graphic display of a 049 * graph. A sprite is a graphical representation that can double anywhere in the 050 * graph drawing surface, or be "attached" to nodes or edges. When attached to 051 * an edge, a sprite can be positioned easily at any point along the edge, or 052 * perpendicular to it with one or two coordinates. When attached to a node, a 053 * sprite "orbits" around the node at any given radius and angle around it. 054 * </p> 055 * 056 * <p> 057 * Sprites can have many shapes. Most of the CSS nodes shapes are available for 058 * sprites, but more are possible. Some shapes follow the form of the element 059 * (node or edge) they are attached to. 060 * </p> 061 * 062 * <p> 063 * Sprites can be moved and animated easily along edges, around nodes, or 064 * anywhere on the graph surface. Their shape can change. Some sprites allows to 065 * draw pie charts or statistics, or images. 066 * </p> 067 * 068 * <p> 069 * Sprites are not part of a graph so to speak. Furthermore they make sense only 070 * when a graph is displayed with a viewer that supports sprites. Therefore they 071 * are handled by a {@link SpriteManager} which is always associated to a graph 072 * and is in charge of handling the whole set of sprites, creating them, 073 * enumerating them, and destroying them. 074 * </p> 075 * 076 * <p> 077 * Implementation note: sprites do not exist ! In fact the sprite class only 078 * handles a set of attributes that are stored in the graph (the one associated 079 * with the sprite manager that created the sprite). These special attributes 080 * are handled for you by the sprite class. This technique allows to pass 081 * sprites informations through the I/O system of GraphStream. Indeed sprites 082 * appearing in a graph can therefore be stored in files and retrieved if the 083 * graph file format supports attributes. If this is a dynamic graph format, 084 * like DGS, the whole sprite history is remembered: when it moved, when it 085 * changed, etc. 086 * </p> 087 * 088 * <p> 089 * Second implementation node : often you will need to extend the sprite class. 090 * This is easily possible, but you must remember that you cannot create sprites 091 * yourself, you must use the {@link SpriteManager}. In order to create a sprite 092 * of a special kind, you can either use a {@link SpriteFactory} with the 093 * SpriteManager or the special {@link SpriteManager#addSprite(String, Class)} 094 * method of the SpriteManager. In both cases, the 095 * {@link #init(String, SpriteManager, Values)} method of the sprite will be 096 * called. Override this method to initialise your sprite. 097 * </p> 098 * 099 * @see SpriteManager 100 * @see SpriteFactory 101 */ 102public class Sprite implements Element { 103 // Attribute 104 105 /** 106 * The sprite unique identifier. 107 */ 108 protected String id; 109 110 /** 111 * The identifier prefixed by "ui.sprite.". 112 */ 113 protected String completeId; 114 115 /** 116 * The boss. 117 */ 118 protected SpriteManager manager; 119 120 /** 121 * Current sprite position. 122 */ 123 protected Values position; 124 125 /** 126 * The element this sprite is attached to (or null). 127 */ 128 protected Element attachment; 129 130 // Construction 131 132 /** For the use with {@link #init(String, SpriteManager, Values)}. */ 133 protected Sprite() { 134 } 135 136 /** 137 * New sprite with a given identifier. 138 * 139 * You cannot build sprites yourself, they are created by the sprite 140 * manager. 141 */ 142 protected Sprite(String id, SpriteManager manager) { 143 this(id, manager, null); 144 } 145 146 /** 147 * New sprite with a given identifier. 148 * 149 * You cannot build sprites yourself, they are created by the sprite 150 * manager. 151 */ 152 protected Sprite(String id, SpriteManager manager, Values position) { 153 init(id, manager, position); 154 } 155 156 /** 157 * New sprite with a given identifier. 158 * 159 * You cannot build sprites yourself, they are created by the sprite 160 * managern. This method is used by the manager when creating instances of 161 * sprites that inherit this class. If you derive the sprite class you can 162 * override this method to initialise your sprite. It is always called when 163 * creating the sprite. 164 */ 165 protected void init(String id, SpriteManager manager, Values position) { 166 this.id = id; 167 this.completeId = String.format("ui.sprite.%s", id); 168 this.manager = manager; 169 170 if (!manager.graph.hasAttribute(completeId)) { 171 if (position != null) { 172 manager.graph.addAttribute(completeId, position); 173 this.position = position; 174 } else { 175 this.position = new Values(Style.Units.GU, 0f, 0f, 0f); 176 manager.graph.addAttribute(completeId, this.position); 177 } 178 } else { 179 if (position != null) { 180 manager.graph.setAttribute(completeId, position); 181 this.position = position; 182 } else { 183 this.position = SpriteManager.getPositionValue(manager.graph 184 .getAttribute(completeId)); 185 } 186 } 187 } 188 189 /** 190 * Called by the manager when the sprite is removed. 191 */ 192 protected void removed() { 193 manager.graph.removeAttribute(completeId); 194 195 String start = String.format("%s.", completeId); 196 197 if (attached()) 198 detach(); 199 200 ArrayList<String> keys = new ArrayList<String>(); 201 202 for (String key : manager.graph.getAttributeKeySet()) { 203 if (key.startsWith(start)) 204 keys.add(key); 205 } 206 207 for (String key : keys) 208 manager.graph.removeAttribute(key); 209 } 210 211 // Access 212 213 /** 214 * The element the sprite is attached to or null if the sprite is not 215 * attached. 216 * 217 * @return An element the sprite is attached to or null. 218 */ 219 public Element getAttachment() { 220 return attachment; 221 } 222 223 /** 224 * True if attached to an edge or node. 225 * 226 * @return False if not attached. 227 */ 228 public boolean attached() { 229 return (attachment != null); 230 } 231 232 /** 233 * X position. 234 * 235 * @return The position in abscissa. 236 */ 237 public double getX() { 238 if (position.values.size() > 0) 239 return position.values.get(0); 240 241 return 0; 242 } 243 244 /** 245 * Y position. 246 * 247 * @return The position in ordinate. 248 */ 249 public double getY() { 250 if (position.values.size() > 1) 251 return position.values.get(1); 252 253 return 0; 254 } 255 256 /** 257 * Z position. 258 * 259 * @return The position in depth. 260 */ 261 public double getZ() { 262 if (position.values.size() > 2) 263 return position.values.get(2); 264 265 return 0; 266 } 267 268 public Style.Units getUnits() { 269 return position.units; 270 } 271 272 // Command 273 274 /** 275 * Attach the sprite to a node with the given identifier. If needed the 276 * sprite is first detached. If the given node identifier does not exist, 277 * the sprite stays in detached state. 278 * 279 * @param id 280 * Identifier of the node to attach to. 281 */ 282 public void attachToNode(String id) { 283 if (attachment != null) 284 detach(); 285 286 attachment = manager.graph.getNode(id); 287 288 if (attachment != null) 289 attachment.addAttribute(completeId); 290 } 291 292 /** 293 * Attach the sprite to an edge with the given identifier. If needed the 294 * sprite is first detached. If the given edge identifier does not exist, 295 * the sprite stays in detached state. 296 * 297 * @param id 298 * Identifier of the edge to attach to. 299 */ 300 public void attachToEdge(String id) { 301 if (attachment != null) 302 detach(); 303 304 attachment = manager.graph.getEdge(id); 305 306 if (attachment != null) 307 attachment.addAttribute(completeId); 308 } 309 310 /** 311 * Detach the sprite from the element it is attached to (if any). 312 */ 313 public void detach() { 314 if (attachment != null) { 315 attachment.removeAttribute(completeId); 316 attachment = null; 317 } 318 } 319 320 public void setPosition(double percent) { 321 setPosition(position.units, percent, 0, 0); 322 } 323 324 public void setPosition(double x, double y, double z) { 325 setPosition(position.units, x, y, z); 326 } 327 328 public void setPosition(Style.Units units, double x, double y, double z) { 329 boolean changed = false; 330 331 if (position.get(0) != x) { 332 changed = true; 333 position.setValue(0, x); 334 } 335 if (position.get(1) != y) { 336 changed = true; 337 position.setValue(1, y); 338 } 339 if (position.get(2) != z) { 340 changed = true; 341 position.setValue(2, z); 342 } 343 if (position.units != units) { 344 changed = true; 345 position.setUnits(units); 346 } 347 348 if (changed) 349 manager.graph.setAttribute(completeId, new Values(position)); 350 } 351 352 protected void setPosition(Values values) { 353 if (values != null) { 354 int n = values.values.size(); 355 356 if (n > 2) { 357 setPosition(values.units, values.get(0), values.get(1), 358 values.get(2)); 359 } else if (n > 0) { 360 setPosition(values.get(0)); 361 } 362 } 363 } 364 365 // Access (Element) 366 367 public String getId() { 368 return id; 369 } 370 371 public CharSequence getLabel(String key) { 372 return manager.graph.getLabel(String.format("%s.%s", completeId, key)); 373 } 374 375 public <T> T getAttribute(String key) { 376 return manager.graph.getAttribute(String.format("%s.%s", completeId, 377 key)); 378 } 379 380 public <T> T getAttribute(String key, Class<T> clazz) { 381 return manager.graph.getAttribute( 382 String.format("%s.%s", completeId, key), clazz); 383 } 384 385 /** 386 * Quite expensive operation !. 387 */ 388 public int getAttributeCount() { 389 String start = String.format("%s.", completeId); 390 int count = 0; 391 392 for (String key : manager.graph.getAttributeKeySet()) { 393 if (key.startsWith(start)) 394 count++; 395 } 396 397 return count; 398 } 399 400 public Iterator<String> getAttributeKeyIterator() { 401 throw new RuntimeException("not implemented"); 402 } 403 404 public Iterable<String> getEachAttributeKey() { 405 return getAttributeKeySet(); 406 } 407 408 public Collection<String> getAttributeKeySet() { 409 throw new RuntimeException("not implemented"); 410 } 411 412 public Map<String, Object> getAttributeMap() { 413 throw new RuntimeException("not implemented"); 414 } 415 416 public <T> T getFirstAttributeOf(String... keys) { 417 String completeKeys[] = new String[keys.length]; 418 int i = 0; 419 420 for (String key : keys) { 421 completeKeys[i] = String.format("%s.%s", completeId, key); 422 i++; 423 } 424 425 return manager.graph.getFirstAttributeOf(completeKeys); 426 } 427 428 public <T> T getFirstAttributeOf(Class<T> clazz, String... keys) { 429 String completeKeys[] = new String[keys.length]; 430 int i = 0; 431 432 for (String key : keys) { 433 completeKeys[i] = String.format("%s.%s", completeId, key); 434 i++; 435 } 436 437 return manager.graph.getFirstAttributeOf(clazz, completeKeys); 438 } 439 440 public Object[] getArray(String key) { 441 return manager.graph.getArray(String.format("%s.%s", completeId, key)); 442 } 443 444 public HashMap<?, ?> getHash(String key) { 445 return manager.graph.getHash(String.format("%s.%s", completeId, key)); 446 } 447 448 public double getNumber(String key) { 449 return manager.graph.getNumber(String.format("%s.%s", completeId, key)); 450 } 451 452 public ArrayList<? extends Number> getVector(String key) { 453 return manager.graph.getVector(String.format("%s.%s", completeId, key)); 454 } 455 456 public boolean hasAttribute(String key) { 457 return manager.graph.hasAttribute(String.format("%s.%s", completeId, 458 key)); 459 } 460 461 public boolean hasArray(String key) { 462 return manager.graph.hasArray(String.format("%s.%s", completeId, key)); 463 } 464 465 public boolean hasAttribute(String key, Class<?> clazz) { 466 return manager.graph.hasAttribute( 467 String.format("%s.%s", completeId, key), clazz); 468 } 469 470 public boolean hasHash(String key) { 471 return manager.graph.hasHash(String.format("%s.%s", completeId, key)); 472 } 473 474 public boolean hasLabel(String key) { 475 return manager.graph.hasLabel(String.format("%s.%s", completeId, key)); 476 } 477 478 public boolean hasNumber(String key) { 479 return manager.graph.hasNumber(String.format("%s.%s", completeId, key)); 480 } 481 482 public boolean hasVector(String key) { 483 return manager.graph.hasVector(String.format("%s.%s", completeId, key)); 484 } 485 486 // Commands (Element) 487 488 public void addAttribute(String attribute, Object... values) { 489 manager.graph.addAttribute( 490 String.format("%s.%s", completeId, attribute), values); 491 } 492 493 public void addAttributes(Map<String, Object> attributes) { 494 for (String key : attributes.keySet()) 495 manager.graph.addAttribute(String.format("%s.%s", completeId, key), 496 attributes.get(key)); 497 } 498 499 public void setAttribute(String attribute, Object... values) { 500 manager.graph.setAttribute( 501 String.format("%s.%s", completeId, attribute), values); 502 } 503 504 public void changeAttribute(String attribute, Object... values) { 505 manager.graph.changeAttribute( 506 String.format("%s.%s", completeId, attribute), values); 507 } 508 509 public void clearAttributes() { 510 String start = String.format("%s.", completeId); 511 ArrayList<String> keys = new ArrayList<String>(); 512 513 for (String key : manager.graph.getAttributeKeySet()) { 514 if (key.startsWith(start)) 515 keys.add(key); 516 } 517 518 for (String key : keys) 519 manager.graph.removeAttribute(key); 520 } 521 522 public void removeAttribute(String attribute) { 523 manager.graph.removeAttribute(String.format("%s.%s", completeId, 524 attribute)); 525 } 526 527 // XXX -> UGLY FIX 528 // Sprites do not have unique index but is this useful? 529 public int getIndex() { 530 return 0; 531 } 532}