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.graphicGraph; 033 034import java.util.ArrayList; 035import java.util.Collection; 036import java.util.HashMap; 037import java.util.HashSet; 038import java.util.Iterator; 039 040import org.graphstream.graph.Element; 041import org.graphstream.ui.graphicGraph.GraphicElement.SwingElementRenderer; 042import org.graphstream.ui.graphicGraph.stylesheet.Rule; 043import org.graphstream.ui.graphicGraph.stylesheet.Selector; 044import org.graphstream.ui.graphicGraph.stylesheet.Style; 045 046/** 047 * A group of graph elements that share the same style. 048 * 049 * <p> 050 * The purpose of a style group is to allow retrieving all elements with the 051 * same style easily. Most of the time, with graphic engines, pushing the 052 * graphic state (the style, colors, line width, textures, gradients) is a 053 * costly operation. Doing it once for several elements can speed up things a 054 * lot. This is the purpose of the style group. 055 * </p> 056 * 057 * <p> 058 * The action of drawing elements in group (first push style, then draw all 059 * elements) are called bulk drawing. All elements that can be drawn at once 060 * this way are called bulk elements. 061 * </p> 062 * 063 * <p> 064 * In a style group it is not always possible do draw elements in a such a 065 * "bulk" operation. If the style contains "dynamic values" for example, that is 066 * value that depend on the value of an attribute stored on the element, or if 067 * the element is modified by an event (clicked, selected), the element will not 068 * be drawn the same as others. 069 * </p> 070 * 071 * <p> 072 * The style group provides iterators on each of these categories of elements : 073 * <ul> 074 * <li>{@link #elements()} allows to browse all elements contained in the group 075 * without exception.</li> 076 * <li>{@link #dynamicElements()} allows to browse the subset of elements having 077 * a attribute that modify their style.</li> 078 * <li>{@link #elementsEvents()} allows to browse the subset of elements 079 * modified by an event.</li> 080 * <li>{@link #bulkElements()} allows to browse all remaining elements that have 081 * no dynamic attribute or event.</li> 082 * </ul> 083 * The calling the three last iterators would yield the same elements as calling 084 * the first one. When drawing you can optimise the drawing by first pushing the 085 * graphic state and then drawing at once all bulk elements. If the dynamic and 086 * event subsets are not empty you then must draw such elements modifying the 087 * graphic state for each one. 088 * </p> 089 */ 090public class StyleGroup extends Style implements Iterable<Element> { 091 // Attribute 092 093 /** 094 * The group unique identifier. 095 */ 096 protected String id; 097 098 /** 099 * The set of style rules. 100 */ 101 protected ArrayList<Rule> rules = new ArrayList<Rule>(); 102 103 /** 104 * Graph elements of this group. 105 */ 106 protected HashMap<String, Element> elements = new HashMap<String, Element>(); 107 108 /** 109 * The global events actually occurring. 110 */ 111 protected StyleGroupSet.EventSet eventSet; 112 113 /** 114 * Set of elements whose style is actually modified individually by an 115 * event. Such elements must be rendered one by one, not in groups like 116 * others. 117 */ 118 protected HashMap<Element, ElementEvents> eventsFor; 119 120 /** 121 * Set of elements that have some dynamic style values. Such elements must 122 * be rendered one by one, not in groups, like others. 123 */ 124 protected HashSet<Element> dynamicOnes; 125 126 /** 127 * A set of events actually pushed only for this group. 128 */ 129 protected String[] curEvents; 130 131 /** 132 * The set of bulk elements. 133 */ 134 protected BulkElements bulkElements = new BulkElements(); 135 136 /** 137 * Associated renderers. 138 */ 139 public HashMap<String, SwingElementRenderer> renderers; 140 141 // Construction 142 143 /** 144 * New style group for a first graph element and the set of style rules that 145 * matches it. More graph elements can be added later. 146 * 147 * @param identifier 148 * The unique group identifier (see 149 * {@link org.graphstream.ui.graphicGraph.stylesheet.StyleSheet#getStyleGroupIdFor(Element, ArrayList)} 150 * ). 151 * @param rules 152 * The set of style rules for the style group (see 153 * {@link org.graphstream.ui.graphicGraph.stylesheet.StyleSheet#getRulesFor(Element)} 154 * ). 155 * @param firstElement 156 * The first element to construct the group. 157 */ 158 public StyleGroup(String identifier, Collection<Rule> rules, 159 Element firstElement, StyleGroupSet.EventSet eventSet) { 160 this.id = identifier; 161 this.rules.addAll(rules); 162 this.elements.put(firstElement.getId(), firstElement); 163 this.values = null; // To avoid consume memory since this style will not 164 // store anything. 165 this.eventSet = eventSet; 166 167 for (Rule rule : rules) 168 rule.addGroup(identifier); 169 } 170 171 // Access 172 173 /** 174 * The group unique identifier. 175 * 176 * @return A style group identifier. 177 */ 178 public String getId() { 179 return id; 180 } 181 182 /** 183 * Type of graph element concerned by this style (node, edge, sprite, 184 * graph). 185 * 186 * @return The type of the style group elements. 187 */ 188 public Selector.Type getType() { 189 return rules.get(0).selector.type; 190 } 191 192 /** 193 * True if at least one of the style properties is dynamic (set according to 194 * an attribute of the element to draw). Such elements cannot therefore be 195 * drawn in a group operation, but one by one. 196 * 197 * @return True if one property is dynamic. 198 */ 199 public boolean hasDynamicElements() { 200 return (dynamicOnes != null && dynamicOnes.size() > 0); 201 } 202 203 /** 204 * If true this group contains some elements that are actually changed by an 205 * event. Such elements cannot therefore be drawn in a group operation, but 206 * one by one. 207 * 208 * @return True if the group contains some elements changed by an event. 209 */ 210 public boolean hasEventElements() { 211 return (eventsFor != null && eventsFor.size() > 0); 212 } 213 214 /** 215 * True if the given element actually has active events. 216 * 217 * @param element 218 * The element to test. 219 * @return True if the element has actually active events. 220 */ 221 public boolean elementHasEvents(Element element) { 222 return (eventsFor != null && eventsFor.containsKey(element)); 223 } 224 225 /** 226 * True if the given element has dynamic style values provided by specific 227 * attributes. 228 * 229 * @param element 230 * The element to test. 231 * @return True if the element has actually specific style attributes. 232 */ 233 public boolean elementIsDynamic(Element element) { 234 return (dynamicOnes != null && dynamicOnes.contains(element)); 235 } 236 237 /** 238 * Get the value of a given property. 239 * 240 * This is a redefinition of the method in {@link Style} to consider the 241 * fact a style group aggregates several style rules. 242 * 243 * @param property 244 * The style property the value is searched for. 245 */ 246 @Override 247 public Object getValue(String property, String... events) { 248 int n = rules.size(); 249 250 if (events == null || events.length == 0) { 251 if (curEvents != null && curEvents.length > 0) { 252 events = curEvents; 253 } else if (eventSet.events != null && eventSet.events.length > 0) { 254 events = eventSet.events; 255 } 256 } 257 258 for (int i = 1; i < n; i++) { 259 Style style = rules.get(i).getStyle(); 260 261 if (style.hasValue(property, events)) 262 return style.getValue(property, events); 263 } 264 265 return rules.get(0).getStyle().getValue(property, events); 266 } 267 268 /** 269 * True if there are no elements in the group. 270 * 271 * @return True if the group is empty of elements. 272 */ 273 public boolean isEmpty() { 274 return elements.isEmpty(); 275 } 276 277 /** 278 * True if the group contains the element whose identifier is given. 279 * 280 * @param elementId 281 * The element to search. 282 * @return true if the element is in the group. 283 */ 284 public boolean contains(String elementId) { 285 return elements.containsKey(elementId); 286 } 287 288 /** 289 * True if the group contains the element given. 290 * 291 * @param element 292 * The element to search. 293 * @return true if the element is in the group. 294 */ 295 public boolean contains(Element element) { 296 return elements.containsKey(element.getId()); 297 } 298 299 /** 300 * Return an element of the group, knowing its identifier. 301 * 302 * @param id 303 * The searched element identifier. 304 * @return The element corresponding to the identifier or null if not found. 305 */ 306 public Element getElement(String id) { 307 return elements.get(id); 308 } 309 310 /** 311 * The number of elements of the group. 312 * 313 * @return The element count. 314 */ 315 public int getElementCount() { 316 return elements.size(); 317 } 318 319 /** 320 * Iterator on the set of graph elements of this group. 321 * 322 * @return The elements iterator. 323 */ 324 public Iterator<? extends Element> getElementIterator() { 325 return elements.values().iterator(); 326 } 327 328 /** 329 * Iterable set of elements. This the complete set of elements contained in 330 * this group without regard to the fact they are modified by an event or 331 * are dynamic. If you plan to respect events or dynamic elements, you must 332 * check the elements are not modified by events using 333 * {@link #elementHasEvents(Element)} and are not dynamic by using 334 * {@link #elementIsDynamic(Element)} and then draw modified elements using 335 * {@link #elementsEvents()} and {@link #dynamicElements()}. But the easiest 336 * way of drawing is to use first {@link #bulkElements()} for all non 337 * dynamic non event elements, then the {@link #dynamicElements()} and 338 * {@link #elementsEvents()} to draw all dynamic and event elements. 339 * 340 * @return All the elements in no particular order. 341 */ 342 public Iterable<? extends Element> elements() { 343 return elements.values(); 344 } 345 346 /** 347 * Iterable set of elements that can be drawn in a bulk operation, that is 348 * the subset of all elements that are not dynamic or modified by an event. 349 * 350 * @return The iterable set of bulk elements. 351 */ 352 public Iterable<? extends Element> bulkElements() { 353 return bulkElements; 354 } 355 356 /** 357 * Subset of elements that are actually modified by one or more events. The 358 * {@link ElementEvents} class contains the element and an array of events 359 * that can be pushed on the style group set. 360 * 361 * @return The subset of elements modified by one or more events. 362 */ 363 public Iterable<ElementEvents> elementsEvents() { 364 return eventsFor.values(); 365 } 366 367 /** 368 * Subset of elements that have dynamic style values and therefore must be 369 * rendered one by one, not in groups like others. Even though elements 370 * style can specify some dynamics, the elements must individually have 371 * attributes that specify the dynamic value. If the elements do not have 372 * these attributes they can be rendered in bulk operations. 373 * 374 * @return The subset of dynamic elements of the group. 375 */ 376 public Iterable<Element> dynamicElements() { 377 return dynamicOnes; 378 } 379 380 public Iterator<Element> iterator() { 381 return elements.values().iterator(); 382 } 383 384 /** 385 * The associated renderers. 386 * 387 * @return A renderer or null if not found. 388 */ 389 public SwingElementRenderer getRenderer(String id) { 390 if (renderers != null) 391 return renderers.get(id); 392 393 return null; 394 } 395 396 /** 397 * Set of events for a given element or null if the element has not 398 * currently occurring events. 399 * 400 * @return A set of events or null if none occurring at that time. 401 */ 402 public ElementEvents getEventsFor(Element element) { 403 if (eventsFor != null) 404 return eventsFor.get(element); 405 406 return null; 407 } 408 409 /** 410 * Test if an element is pushed as dynamic. 411 */ 412 public boolean isElementDynamic(Element element) { 413 if (dynamicOnes != null) 414 return dynamicOnes.contains(element); 415 416 return false; 417 } 418 419 // Command 420 421 /** 422 * Add a new graph element to the group. 423 * 424 * @param element 425 * The new graph element to add. 426 */ 427 public void addElement(Element element) { 428 elements.put(element.getId(), element); 429 } 430 431 /** 432 * Remove a graph element from the group. 433 * 434 * @param element 435 * The element to remove. 436 * @return The removed element, or null if the element was not found. 437 */ 438 public Element removeElement(Element element) { 439 if (eventsFor != null && eventsFor.containsKey(element)) 440 eventsFor.remove(element); // Remove an eventual remaining event. 441 442 if (dynamicOnes != null && dynamicOnes.contains(element)) 443 dynamicOnes.remove(element); // Remove an eventual remaining dynamic 444 // information. 445 446 return elements.remove(element.getId()); 447 } 448 449 /** 450 * Push an event specifically for the given element. Events are stacked in 451 * order. Called by the GraphicElement. 452 * 453 * @param element 454 * The element to modify with an event. 455 * @param event 456 * The event to push. 457 */ 458 protected void pushEventFor(Element element, String event) { 459 if (elements.containsKey(element.getId())) { 460 if (eventsFor == null) 461 eventsFor = new HashMap<Element, ElementEvents>(); 462 463 ElementEvents evs = eventsFor.get(element); 464 465 if (evs == null) { 466 evs = new ElementEvents(element, this, event); 467 eventsFor.put(element, evs); 468 } else { 469 evs.pushEvent(event); 470 } 471 } 472 } 473 474 /** 475 * Pop an event for the given element. Called by the GraphicElement. 476 * 477 * @param element 478 * The element. 479 * @param event 480 * The event. 481 */ 482 protected void popEventFor(Element element, String event) { 483 if (elements.containsKey(element.getId())) { 484 ElementEvents evs = eventsFor.get(element); 485 486 if (evs != null) { 487 evs.popEvent(event); 488 489 if (evs.eventCount() == 0) 490 eventsFor.remove(element); 491 } 492 493 if (eventsFor.isEmpty()) 494 eventsFor = null; 495 } 496 } 497 498 /** 499 * Before drawing an element that has events, use this method to activate 500 * the events, the style values will be modified accordingly. Events for 501 * this element must have been registered via 502 * {@link #pushEventFor(Element, String)}. After rendering the 503 * {@link #deactivateEvents()} MUST be called. 504 * 505 * @param element 506 * The element to push events for. 507 */ 508 public void activateEventsFor(Element element) { 509 ElementEvents evs = eventsFor.get(element); 510 511 if (evs != null && curEvents == null) 512 curEvents = evs.events(); 513 } 514 515 /** 516 * De-activate any events activated for an element. This method MUST be 517 * called if {@link #activateEventsFor(Element)} has been called. 518 */ 519 public void deactivateEvents() { 520 curEvents = null; 521 } 522 523 /** 524 * Indicate the element has dynamic values and thus cannot be drawn in bulk 525 * operations. Called by the GraphicElement. 526 * 527 * @param element 528 * The element. 529 */ 530 protected void pushElementAsDynamic(Element element) { 531 if (dynamicOnes == null) 532 dynamicOnes = new HashSet<Element>(); 533 534 dynamicOnes.add(element); 535 } 536 537 /** 538 * Indicate the element has no more dynamic values and can be drawn in bulk 539 * operations. Called by the GraphicElement. 540 * 541 * @param element 542 * The element. 543 */ 544 protected void popElementAsDynamic(Element element) { 545 dynamicOnes.remove(element); 546 547 if (dynamicOnes.isEmpty()) 548 dynamicOnes = null; 549 } 550 551 /** 552 * Remove all graph elements of this group, and remove this group from the 553 * group list of each style rule. 554 */ 555 public void release() { 556 for (Rule rule : rules) 557 rule.removeGroup(id); 558 559 elements.clear(); 560 } 561 562 /** 563 * Redefinition of the {@link Style} to forbid changing the values. 564 */ 565 @Override 566 public void setValue(String property, Object value) { 567 throw new RuntimeException( 568 "you cannot change the values of a style group."); 569 } 570 571 /** 572 * Add a renderer to this group. 573 * 574 * @param id 575 * The renderer identifier. 576 * @param renderer 577 * The renderer. 578 */ 579 public void addRenderer(String id, SwingElementRenderer renderer) { 580 if (renderers == null) 581 renderers = new HashMap<String, SwingElementRenderer>(); 582 583 renderers.put(id, renderer); 584 } 585 586 /** 587 * Remove a renderer. 588 * 589 * @param id 590 * The renderer identifier. 591 * @return The removed renderer or null if not found. 592 */ 593 public SwingElementRenderer removeRenderer(String id) { 594 return renderers.remove(id); 595 } 596 597 @Override 598 public String toString() { 599 return toString(-1); 600 } 601 602 @Override 603 public String toString(int level) { 604 StringBuilder builder = new StringBuilder(); 605 String prefix = ""; 606 String sprefix = " "; 607 608 for (int i = 0; i < level; i++) 609 prefix += sprefix; 610 611 builder.append(String.format("%s%s%n", prefix, id)); 612 builder.append(String.format("%s%sContains : ", prefix, sprefix)); 613 614 for (Element element : elements.values()) { 615 builder.append(String.format("%s ", element.getId())); 616 } 617 618 builder.append(String.format("%n%s%sStyle : ", prefix, sprefix)); 619 620 for (Rule rule : rules) { 621 builder.append(String.format("%s ", rule.selector.toString())); 622 } 623 624 builder.append(String.format("%n")); 625 626 return builder.toString(); 627 } 628 629 // Nested classes 630 631 /** 632 * Description of an element that is actually modified by one or more events 633 * occurring on it. 634 */ 635 public static class ElementEvents { 636 // Attribute 637 638 /** 639 * Set of events on the element. 640 */ 641 protected String events[]; 642 643 /** 644 * The element. 645 */ 646 protected Element element; 647 648 /** 649 * The group the element pertains to. 650 */ 651 protected StyleGroup group; 652 653 // Construction 654 655 protected ElementEvents(Element element, StyleGroup group, String event) { 656 this.element = element; 657 this.group = group; 658 this.events = new String[1]; 659 660 events[0] = event; 661 } 662 663 // Access 664 665 /** 666 * The element on which the events are occurring. 667 * 668 * @return an element. 669 */ 670 public Element getElement() { 671 return element; 672 } 673 674 /** 675 * Number of events actually affecting the element. 676 * 677 * @return The number of events affecting the element. 678 */ 679 public int eventCount() { 680 if (events == null) 681 return 0; 682 683 return events.length; 684 } 685 686 /** 687 * The set of events actually occurring on the element. 688 * 689 * @return A set of strings. 690 */ 691 public String[] events() { 692 return events; 693 } 694 695 // Command 696 697 public void activate() { 698 group.activateEventsFor(element); 699 } 700 701 public void deactivate() { 702 group.deactivateEvents(); 703 } 704 705 protected void pushEvent(String event) { 706 int n = events.length + 1; 707 String e[] = new String[n]; 708 boolean found = false; 709 710 for (int i = 0; i < events.length; i++) { 711 if (!events[i].equals(event)) 712 e[i] = events[i]; 713 else 714 found = true; 715 } 716 717 e[events.length] = event; 718 719 if (!found) 720 events = e; 721 } 722 723 protected void popEvent(String event) { 724 if (events.length > 1) { 725 String e[] = new String[events.length - 1]; 726 boolean found = false; 727 728 for (int i = 0, j = 0; i < events.length; i++) { 729 if (!events[i].equals(event)) { 730 if (j < e.length) { 731 e[j++] = events[i]; 732 } 733 } else { 734 found = true; 735 } 736 } 737 738 if (found) 739 events = e; 740 } else { 741 if (events[0].equals(event)) { 742 events = null; 743 } 744 } 745 } 746 747 @Override 748 public String toString() { 749 StringBuilder builder = new StringBuilder(); 750 751 builder.append(String.format("%s events {", element.getId())); 752 for (String event : events) 753 builder.append(String.format(" %s", event)); 754 builder.append(" }"); 755 756 return builder.toString(); 757 } 758 } 759 760 /** 761 * Virtual set on the elements that have not dynamic style value or event. 762 */ 763 protected class BulkElements implements Iterable<Element> { 764 public Iterator<Element> iterator() { 765 return new BulkIterator(elements.values().iterator()); 766 } 767 } 768 769 /** 770 * Iterator on the set of elements that have no event or dynamic style 771 * values. 772 */ 773 protected class BulkIterator implements Iterator<Element> { 774 /** 775 * Iterator on the set of all elements. 776 */ 777 protected Iterator<Element> iterator; 778 779 /** 780 * The next element without event or dynamic style.value. 781 */ 782 Element next; 783 784 /** 785 * New bulk iterator positioned on the first element with no event or 786 * dynamic style attribute. 787 * 788 * @param iterator 789 * Iterator on the set of all elements. 790 */ 791 public BulkIterator(Iterator<Element> iterator) { 792 this.iterator = iterator; 793 boolean loop = true; 794 795 while (loop && iterator.hasNext()) { 796 next = iterator.next(); 797 798 if (!elementHasEvents(next) && !elementIsDynamic(next)) 799 loop = false; 800 else 801 next = null; 802 } 803 } 804 805 public boolean hasNext() { 806 return (next != null); 807 } 808 809 public Element next() { 810 Element e = next; 811 boolean loop = true; 812 813 next = null; 814 815 while (loop && iterator.hasNext()) { 816 next = iterator.next(); 817 818 if (!elementIsDynamic(next) && !elementHasEvents(next)) 819 loop = false; 820 else 821 next = null; 822 } 823 824 return e; 825 } 826 827 public void remove() { 828 throw new UnsupportedOperationException( 829 "this iterator does not allows removing elements"); 830 } 831 } 832}