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}