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.HashMap; 036import java.util.HashSet; 037import java.util.Iterator; 038 039import org.graphstream.graph.Edge; 040import org.graphstream.graph.Element; 041import org.graphstream.graph.Graph; 042import org.graphstream.graph.Node; 043import org.graphstream.ui.graphicGraph.stylesheet.Rule; 044import org.graphstream.ui.graphicGraph.stylesheet.Selector; 045import org.graphstream.ui.graphicGraph.stylesheet.StyleSheet; 046import org.graphstream.ui.graphicGraph.stylesheet.StyleSheetListener; 047import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.ShadowMode; 048 049/** 050 * A set of style groups. 051 * 052 * <p> 053 * This class is in charge or storing all the style groups and to update them. 054 * Each time an element is added or removed the groups are updated. Each time 055 * the style sheet changes the groups are updated. 056 * </p> 057 * 058 * @author Antoine Dutot 059 */ 060public class StyleGroupSet implements StyleSheetListener { 061 // Attribute 062 063 /** 064 * The style sheet. 065 */ 066 protected StyleSheet stylesheet; 067 068 /** 069 * All the groups indexed by their unique identifier. 070 */ 071 protected HashMap<String, StyleGroup> groups = new HashMap<String, StyleGroup>(); 072 073 /** 074 * Allows to retrieve the group containing a node knowing the node id. 075 */ 076 protected HashMap<String, String> byNodeIdGroups = new HashMap<String, String>(); 077 078 /** 079 * Allows to retrieve the group containing an edge knowing the node id. 080 */ 081 protected HashMap<String, String> byEdgeIdGroups = new HashMap<String, String>(); 082 083 /** 084 * Allows to retrieve the group containing a sprite knowing the node id. 085 */ 086 protected HashMap<String, String> bySpriteIdGroups = new HashMap<String, String>(); 087 088 /** 089 * Allows to retrieve the group containing a graph knowing the node id. 090 */ 091 protected HashMap<String, String> byGraphIdGroups = new HashMap<String, String>(); 092 093 /** 094 * Virtual set of nodes. This set provides fake methods to make it appear as 095 * a set of nodes whereas it only maps on the node style groups. 096 */ 097 protected NodeSet nodeSet = new NodeSet(); 098 099 /** 100 * Virtual set of edges. This set provides fake methods to make it appear as 101 * a set of edges whereas it only maps on the edge style groups. 102 */ 103 protected EdgeSet edgeSet = new EdgeSet(); 104 105 /** 106 * Virtual set of sprites. This set provides fake methods to make it appear 107 * as a set of sprites whereas it only maps on the sprite style groups. 108 */ 109 protected SpriteSet spriteSet = new SpriteSet(); 110 111 /** 112 * Virtual set of graphs. This set provides fake methods to make it appear 113 * as a set of graphs whereas it only maps on the graph style groups. 114 */ 115 protected GraphSet graphSet = new GraphSet(); 116 117 /** 118 * The set of events actually occurring. 119 */ 120 protected EventSet eventSet = new EventSet(); 121 122 /** 123 * The groups sorted by their Z index. 124 */ 125 protected ZIndex zIndex = new ZIndex(); 126 127 /** 128 * Set of groups that cast shadow. 129 */ 130 protected ShadowSet shadow = new ShadowSet(); 131 132 /** 133 * Remove groups if they become empty?. 134 */ 135 protected boolean removeEmptyGroups = true; 136 137 /** 138 * Set of listeners. 139 */ 140 protected ArrayList<StyleGroupListener> listeners = new ArrayList<StyleGroupListener>(); 141 142 // Construction 143 144 /** 145 * New empty style group set, using the given style sheet to create style 146 * groups. The group set installs itself as a listener of the style sheet. 147 * So in order to completely stop using such a group, you must call 148 * {@link #release()}. 149 * 150 * @param stylesheet 151 * The style sheet to use to create groups. 152 */ 153 public StyleGroupSet(StyleSheet stylesheet) { 154 this.stylesheet = stylesheet; 155 156 stylesheet.addListener(this); 157 } 158 159 // Access 160 161 /** 162 * Number of groups. 163 * 164 * @return The number of groups. 165 */ 166 public int getGroupCount() { 167 return groups.size(); 168 } 169 170 /** 171 * Return a group by its unique identifier. The way group identifier are 172 * constructed reflects their contents. 173 * 174 * @param groupId 175 * The group identifier. 176 * @return The corresponding group or null if not found. 177 */ 178 public StyleGroup getGroup(String groupId) { 179 return groups.get(groupId); 180 } 181 182 /** 183 * Iterator on the set of groups in no particular order. 184 * 185 * @return An iterator on the group set. 186 */ 187 public Iterator<? extends StyleGroup> getGroupIterator() { 188 return groups.values().iterator(); 189 } 190 191 /** 192 * Iterable set of groups elements, in no particular order. 193 * 194 * @return An iterable on the set of groups. 195 */ 196 public Iterable<? extends StyleGroup> groups() { 197 return groups.values(); 198 } 199 200 /** 201 * Iterator on the Z index. 202 * 203 * @return The z index iterator. 204 */ 205 public Iterator<HashSet<StyleGroup>> getZIterator() { 206 return zIndex.getIterator(); 207 } 208 209 /** 210 * Iterable set of "subsets of groups" sorted by Z level. Each subset of 211 * groups is at the same Z level. 212 * 213 * @return The z levels. 214 */ 215 public Iterable<HashSet<StyleGroup>> zIndex() { 216 return zIndex; 217 } 218 219 /** 220 * Iterator on the style groups that cast a shadow. 221 * 222 * @return The shadow groups iterator. 223 */ 224 public Iterator<StyleGroup> getShadowIterator() { 225 return shadow.getIterator(); 226 } 227 228 /** 229 * Iterable set of groups that cast shadow. 230 * 231 * @return All the groups that cast a shadow. 232 */ 233 public Iterable<StyleGroup> shadows() { 234 return shadow; 235 } 236 237 /** 238 * True if the set contains and styles the node whose identifier is given. 239 * 240 * @param id 241 * The node identifier. 242 * @return True if the node is in this set. 243 */ 244 public boolean containsNode(String id) { 245 return byNodeIdGroups.containsKey(id); 246 } 247 248 /** 249 * True if the set contains and styles the edge whose identifier is given. 250 * 251 * @param id 252 * The edge identifier. 253 * @return True if the edge is in this set. 254 */ 255 public boolean containsEdge(String id) { 256 return byEdgeIdGroups.containsKey(id); 257 } 258 259 /** 260 * True if the set contains and styles the sprite whose identifier is given. 261 * 262 * @param id 263 * The sprite identifier. 264 * @return True if the sprite is in this set. 265 */ 266 public boolean containsSprite(String id) { 267 return bySpriteIdGroups.containsKey(id); 268 } 269 270 /** 271 * True if the set contains and styles the graph whose identifier is given. 272 * 273 * @param id 274 * The graph identifier. 275 * @return True if the graph is in this set. 276 */ 277 public boolean containsGraph(String id) { 278 return byGraphIdGroups.containsKey(id); 279 } 280 281 /** 282 * Get an element. 283 * 284 * @param id 285 * The element id. 286 * @param elt2grp 287 * The kind of element. 288 * @return The element or null if not found. 289 */ 290 protected Element getElement(String id, HashMap<String, String> elt2grp) { 291 String gid = elt2grp.get(id); 292 293 if (gid != null) { 294 StyleGroup group = groups.get(gid); 295 return group.getElement(id); 296 } 297 298 return null; 299 } 300 301 /** 302 * Get a node element knowing its identifier. 303 * 304 * @param id 305 * The node identifier. 306 * @return The node if it is in this set, else null. 307 */ 308 public Node getNode(String id) { 309 return (Node) getElement(id, byNodeIdGroups); 310 } 311 312 /** 313 * Get an edge element knowing its identifier. 314 * 315 * @param id 316 * The edge identifier. 317 * @return The edge if it is in this set, else null. 318 */ 319 public Edge getEdge(String id) { 320 return (Edge) getElement(id, byEdgeIdGroups); 321 } 322 323 /** 324 * Get a sprite element knowing its identifier. 325 * 326 * @param id 327 * The sprite identifier. 328 * @return The sprite if it is in this set, else null. 329 */ 330 public GraphicSprite getSprite(String id) { 331 return (GraphicSprite) getElement(id, bySpriteIdGroups); 332 } 333 334 /** 335 * Get a graph element knowing its identifier. 336 * 337 * @param id 338 * The graph identifier. 339 * @return The graph if it is in this set, else null. 340 */ 341 public Graph getGraph(String id) { 342 return (Graph) getElement(id, byGraphIdGroups); 343 } 344 345 /** 346 * The number of nodes referenced. 347 * 348 * @return The node count. 349 */ 350 public int getNodeCount() { 351 return byNodeIdGroups.size(); 352 } 353 354 /** 355 * The number of edges referenced. 356 * 357 * @return The edge count. 358 */ 359 public int getEdgeCount() { 360 return byEdgeIdGroups.size(); 361 } 362 363 /** 364 * The number of sprites referenced. 365 * 366 * @return The sprite count. 367 */ 368 public int getSpriteCount() { 369 return bySpriteIdGroups.size(); 370 } 371 372 /** 373 * Iterator on the set of nodes. 374 * 375 * @return An iterator on all node elements contained in style groups. 376 */ 377 public Iterator<? extends Node> getNodeIterator() { 378 return new ElementIterator<Node>(byNodeIdGroups); 379 } 380 381 /** 382 * Iterator on the set of graphs. 383 * 384 * @return An iterator on all graph elements contained in style groups. 385 */ 386 public Iterator<? extends Graph> getGraphIterator() { 387 return new ElementIterator<Graph>(byGraphIdGroups); 388 } 389 390 /** 391 * Iterable set of nodes. 392 * 393 * @return The set of all nodes. 394 */ 395 public Iterable<? extends Node> nodes() { 396 return nodeSet; 397 } 398 399 /** 400 * Iterable set of graphs. 401 * 402 * @return The set of all graphs. 403 */ 404 public Iterable<? extends Graph> graphs() { 405 return graphSet; 406 } 407 408 /** 409 * Iterator on the set of edges. 410 * 411 * @return An iterator on all edge elements contained in style groups. 412 */ 413 public Iterator<? extends Edge> getEdgeIterator() { 414 return new ElementIterator<Edge>(byEdgeIdGroups); 415 } 416 417 /** 418 * Iterable set of edges. 419 * 420 * @return The set of all edges. 421 */ 422 public Iterable<? extends Edge> edges() { 423 return edgeSet; 424 } 425 426 /** 427 * Iterator on the set of sprite. 428 * 429 * @return An iterator on all sprite elements contained in style groups. 430 */ 431 public Iterator<? extends GraphicSprite> getSpriteIterator() { 432 return new ElementIterator<GraphicSprite>(bySpriteIdGroups); 433 } 434 435 /** 436 * Iterable set of sprites. 437 * 438 * @return The set of all sprites. 439 */ 440 public Iterable<? extends GraphicSprite> sprites() { 441 return spriteSet; 442 } 443 444 /** 445 * Retrieve the group identifier of an element knowing the element 446 * identifier. 447 * 448 * @param element 449 * The element to search for. 450 * @return Identifier of the group containing the element. 451 */ 452 public String getElementGroup(Element element) { 453 if (element instanceof Node) { 454 return byNodeIdGroups.get(element.getId()); 455 } else if (element instanceof Edge) { 456 return byEdgeIdGroups.get(element.getId()); 457 } else if (element instanceof GraphicSprite) { 458 return bySpriteIdGroups.get(element.getId()); 459 } else if (element instanceof Graph) { 460 return byGraphIdGroups.get(element.getId()); 461 } else { 462 throw new RuntimeException("What ?"); 463 } 464 } 465 466 /** 467 * Get the style of an element. 468 * 469 * @param element 470 * The element to search for. 471 * @return The style group of the element (which is also a style). 472 */ 473 public StyleGroup getStyleForElement(Element element) { 474 String gid = getElementGroup(element); 475 476 return groups.get(gid); 477 } 478 479 /** 480 * Get the style of a given node. 481 * 482 * @param node 483 * The node to search for. 484 * @return The node style. 485 */ 486 public StyleGroup getStyleFor(Node node) { 487 String gid = byNodeIdGroups.get(node.getId()); 488 return groups.get(gid); 489 } 490 491 /** 492 * Get the style of a given edge. 493 * 494 * @param edge 495 * The edge to search for. 496 * @return The edge style. 497 */ 498 public StyleGroup getStyleFor(Edge edge) { 499 String gid = byEdgeIdGroups.get(edge.getId()); 500 return groups.get(gid); 501 } 502 503 /** 504 * Get the style of a given sprite. 505 * 506 * @param sprite 507 * The node to search for. 508 * @return The sprite style. 509 */ 510 public StyleGroup getStyleFor(GraphicSprite sprite) { 511 String gid = bySpriteIdGroups.get(sprite.getId()); 512 return groups.get(gid); 513 } 514 515 /** 516 * Get the style of a given graph. 517 * 518 * @param graph 519 * The node to search for. 520 * @return The graph style. 521 */ 522 public StyleGroup getStyleFor(Graph graph) { 523 String gid = byGraphIdGroups.get(graph.getId()); 524 return groups.get(gid); 525 } 526 527 /** 528 * True if groups are removed when becoming empty. This setting allows to 529 * keep empty group when the set of elements is quite dynamic. This allows 530 * to avoid recreting groups when an element appears and disappears 531 * regularly. 532 * 533 * @return True if the groups are removed when empty. 534 */ 535 public boolean areEmptyGroupRemoved() { 536 return removeEmptyGroups; 537 } 538 539 /** 540 * The Z index object. 541 * 542 * @return The Z index. 543 */ 544 public ZIndex getZIndex() { 545 return zIndex; 546 } 547 548 /** 549 * The set of style groups that cast a shadow. 550 * 551 * @return The set of shadowed style groups. 552 */ 553 public ShadowSet getShadowSet() { 554 return shadow; 555 } 556 557 // Command 558 559 /** 560 * Release any dependency to the style sheet. 561 */ 562 public void release() { 563 stylesheet.removeListener(this); 564 } 565 566 /** 567 * Empties this style group set. The style sheet is listener is not removed, 568 * use {@link #release()} to do that. 569 */ 570 public void clear() { 571 byEdgeIdGroups.clear(); 572 byNodeIdGroups.clear(); 573 bySpriteIdGroups.clear(); 574 byGraphIdGroups.clear(); 575 groups.clear(); 576 zIndex.clear(); 577 shadow.clear(); 578 } 579 580 /** 581 * Remove or keep groups that becomes empty, if true the groups are removed. 582 * If this setting was set to false, and is now true, the group set is 583 * purged of the empty groups. 584 * 585 * @param on 586 * If true the groups will be removed. 587 */ 588 public void setRemoveEmptyGroups(boolean on) { 589 if (removeEmptyGroups == false && on == true) { 590 Iterator<? extends StyleGroup> i = groups.values().iterator(); 591 592 while (i.hasNext()) { 593 StyleGroup g = i.next(); 594 595 if (g.isEmpty()) 596 i.remove(); 597 } 598 } 599 600 removeEmptyGroups = on; 601 } 602 603 protected StyleGroup addGroup(String id, ArrayList<Rule> rules, 604 Element firstElement) { 605 StyleGroup group = new StyleGroup(id, rules, firstElement, eventSet); 606 607 groups.put(id, group); 608 zIndex.groupAdded(group); 609 shadow.groupAdded(group); 610 611 return group; 612 } 613 614 protected void removeGroup(StyleGroup group) { 615 zIndex.groupRemoved(group); 616 shadow.groupRemoved(group); 617 groups.remove(group.getId()); 618 group.release(); 619 } 620 621 /** 622 * Add an element and bind it to its style group. The group is created if 623 * needed. 624 * 625 * @param element 626 * The element to add. 627 * @return The style group where the element was added. 628 */ 629 public StyleGroup addElement(Element element) { 630 StyleGroup group = addElement_(element); 631 632 for (StyleGroupListener listener : listeners) 633 listener.elementStyleChanged(element, null, group); 634 635 return group; 636 } 637 638 protected StyleGroup addElement_(Element element) { 639 ArrayList<Rule> rules = stylesheet.getRulesFor(element); 640 String gid = stylesheet.getStyleGroupIdFor(element, rules); 641 StyleGroup group = groups.get(gid); 642 643 if (group == null) 644 group = addGroup(gid, rules, element); 645 else 646 group.addElement(element); 647 648 addElementToReverseSearch(element, gid); 649 650 return group; 651 } 652 653 /** 654 * Remove an element from the group set. If the group becomes empty after 655 * the element removal, depending on the setting of 656 * {@link #areEmptyGroupRemoved()}, the group is deleted or kept. Keeping 657 * groups allows to handle faster elements that constantly appear and 658 * disappear. 659 * 660 * @param element 661 * The element to remove. 662 */ 663 public void removeElement(Element element) { 664 String gid = getElementGroup(element); 665 StyleGroup group = groups.get(gid); 666 667 if (group != null) { 668 group.removeElement(element); 669 removeElementFromReverseSearch(element); 670 671 if (removeEmptyGroups && group.isEmpty()) 672 removeGroup(group); 673 } 674 } 675 676 /** 677 * Check if an element need to change from a style group to another. 678 * 679 * <p> 680 * When an element can have potentially changed style due to some of its 681 * attributes (ui.class for example), instead of removing it then reading 682 * it, use this method to move the element from its current style group to a 683 * potentially different style group. 684 * </p> 685 * 686 * <p> 687 * Explanation of this method : checking the style of an element may be done 688 * by removing it ({@link #removeElement(Element)}) and then re-adding it ( 689 * {@link #addElement(Element)}). This must be done by the element since it 690 * knows when to check this. However you cannot only remove and add, since 691 * the style group inside which the element is can have events occurring on 692 * it, and these events must be passed from its old style to its new style. 693 * This method does all this information passing. 694 * </p> 695 * 696 * @param element 697 * The element to move. 698 */ 699 public void checkElementStyleGroup(Element element) { 700 StyleGroup oldGroup = getGroup(getElementGroup(element)); 701 702 // Get the old element "dynamic" status. 703 704 boolean isDyn = false; 705 706 // Get the old event set for the given element. 707 708 StyleGroup.ElementEvents events = null; 709 710 if (oldGroup != null) { 711 isDyn = oldGroup.isElementDynamic(element); 712 events = oldGroup.getEventsFor(element); 713 } 714 715 // Remove the element from its old style and add it to insert it in the 716 // correct style. 717 718 removeElement(element); 719 addElement_(element); 720 721 // Eventually push the events on the new style group. 722 723 StyleGroup newGroup = getGroup(getElementGroup(element)); 724 725 if (newGroup != null && events != null) { 726 for (String event : events.events) 727 pushEventFor(element, event); 728 } 729 730 for (StyleGroupListener listener : listeners) 731 listener.elementStyleChanged(element, oldGroup, newGroup); 732 733 // Eventually set the element as dynamic, if it was. 734 735 if (newGroup != null && isDyn) 736 newGroup.pushElementAsDynamic(element); 737 } 738 739 protected void addElementToReverseSearch(Element element, String groupId) { 740 if (element instanceof Node) { 741 byNodeIdGroups.put(element.getId(), groupId); 742 } else if (element instanceof Edge) { 743 byEdgeIdGroups.put(element.getId(), groupId); 744 } else if (element instanceof GraphicSprite) { 745 bySpriteIdGroups.put(element.getId(), groupId); 746 } else if (element instanceof Graph) { 747 byGraphIdGroups.put(element.getId(), groupId); 748 } else { 749 throw new RuntimeException("What ?"); 750 } 751 } 752 753 protected void removeElementFromReverseSearch(Element element) { 754 if (element instanceof Node) { 755 byNodeIdGroups.remove(element.getId()); 756 } else if (element instanceof Edge) { 757 byEdgeIdGroups.remove(element.getId()); 758 } else if (element instanceof GraphicSprite) { 759 bySpriteIdGroups.remove(element.getId()); 760 } else if (element instanceof Graph) { 761 byGraphIdGroups.remove(element.getId()); 762 } else { 763 throw new RuntimeException("What ?"); 764 } 765 } 766 767 /** 768 * Push a global event on the event stack. Events trigger the replacement of 769 * a style by an alternative style (or meta-class) when possible. If an 770 * event is on the event stack, each time a style has an alternative 771 * corresponding to the event, the alternative is used instead of the style. 772 * 773 * @param event 774 * The event to push. 775 */ 776 public void pushEvent(String event) { 777 eventSet.pushEvent(event); 778 } 779 780 /** 781 * Push an event specifically for a given element. This is normally done 782 * automatically by the graphic element. 783 * 784 * @param element 785 * The element considered. 786 * @param event 787 * The event to push. 788 */ 789 public void pushEventFor(Element element, String event) { 790 StyleGroup group = getGroup(getElementGroup(element)); 791 792 if (group != null) 793 group.pushEventFor(element, event); 794 } 795 796 /** 797 * Pop a global event from the event set. 798 * 799 * @param event 800 * The event to remove. 801 */ 802 public void popEvent(String event) { 803 eventSet.popEvent(event); 804 } 805 806 /** 807 * Pop an event specifically for a given element. This is normally done 808 * automatically by the graphic element. 809 * 810 * @param element 811 * The element considered. 812 * @param event 813 * The event to pop. 814 */ 815 public void popEventFor(Element element, String event) { 816 StyleGroup group = getGroup(getElementGroup(element)); 817 818 if (group != null) 819 group.popEventFor(element, event); 820 } 821 822 /** 823 * Specify the given element has dynamic style attribute values. This is 824 * normally done automatically by the graphic element. 825 * 826 * @param element 827 * The element to add to the dynamic subset. 828 */ 829 public void pushElementAsDynamic(Element element) { 830 StyleGroup group = getGroup(getElementGroup(element)); 831 832 if (group != null) 833 group.pushElementAsDynamic(element); 834 } 835 836 /** 837 * Remove the given element from the subset of elements having dynamic style 838 * attribute values. This is normally done automatically by the graphic 839 * element. 840 * 841 * @param element 842 * The element to remove from the dynamic subset. 843 */ 844 public void popElementAsDynamic(Element element) { 845 StyleGroup group = getGroup(getElementGroup(element)); 846 847 if (group != null) 848 group.popElementAsDynamic(element); 849 } 850 851 /** 852 * Add a listener for element style changes. 853 * 854 * @param listener 855 * The listener to add. 856 */ 857 public void addListener(StyleGroupListener listener) { 858 listeners.add(listener); 859 } 860 861 /** 862 * Remove a style change listener. 863 * 864 * @param listener 865 * The listener to remove. 866 */ 867 public void removeListener(StyleGroupListener listener) { 868 int index = listeners.lastIndexOf(listener); 869 870 if (index >= 0) { 871 listeners.remove(index); 872 } 873 } 874 875 // Listener -- What to do when a change occurs in the style sheet. 876 877 public void styleAdded(Rule oldRule, Rule newRule) { 878 // When a style change, we need to update groups. 879 // Several cases : 880 // 1. The style already exists 881 // * Nothing to do in fact. All the elements are still in place. 882 // No style rule (selectors) changed, and therefore we do not have 883 // to change the groups since they are built using the selectors. 884 // 2. The style is new 885 // * we need to check all the groups concerning this kind of element (we 886 // can 887 // restrict our search to these groups, since other will not be 888 // impacted), 889 // and check all elements of these groups. 890 891 if (oldRule == null) 892 checkForNewStyle(newRule); // no need to check Z and shadow, done 893 // when adding/changing group. 894 else 895 checkZIndexAndShadow(oldRule, newRule); 896 } 897 898 public void styleSheetCleared() { 899 ArrayList<Element> elements = new ArrayList<Element>(); 900 901 for (Element element : graphs()) 902 elements.add(element); 903 904 for (Element element : nodes()) 905 elements.add(element); 906 907 for (Element element : edges()) 908 elements.add(element); 909 910 for (Element element : sprites()) 911 elements.add(element); 912 913 clear(); 914 915 for (Element element : elements) 916 removeElement(element); 917 918 for (Element element : elements) 919 addElement(element); 920 } 921 922 /** 923 * Check each group that may have changed, for example to rebuild the Z 924 * index and the shadow set. 925 * 926 * @param oldRule 927 * The old rule that changed. 928 * @param newRule 929 * The new rule that participated in the change. 930 */ 931 protected void checkZIndexAndShadow(Rule oldRule, Rule newRule) { 932 if (oldRule != null) { 933 if (oldRule.selector.getId() != null 934 || oldRule.selector.getClazz() != null) { 935 // We may accelerate things a bit when a class or id style is 936 // modified, 937 // since only the groups listed in the style are concerned (we 938 // are at the 939 // bottom of the inheritance tree). 940 if (oldRule.getGroups() != null) 941 for (String s : oldRule.getGroups()) { 942 StyleGroup group = groups.get(s); 943 if (group != null) { 944 zIndex.groupChanged(group); 945 shadow.groupChanged(group); 946 } 947 } 948 } else { 949 // For kind styles "NODE", "EDGE", "GRAPH", "SPRITE", we must 950 // reset 951 // the whole Z and shadows for the kind, since several styles 952 // may 953 // have changed. 954 955 Selector.Type type = oldRule.selector.type; 956 957 for (StyleGroup group : groups.values()) { 958 if (group.getType() == type) { 959 zIndex.groupChanged(group); 960 shadow.groupChanged(group); 961 } 962 } 963 } 964 } 965 } 966 967 /** 968 * We try to avoid at most to affect anew styles to elements and to recreate 969 * groups, which is time consuming. 970 * 971 * Two cases : 972 * <ol> 973 * <li>The style is an specific (id) style. In this case a new group may be 974 * added. 975 * <ul> 976 * <li>check an element matches the style and in this case create the group 977 * by adding the element.</li> 978 * <li>else do nothing.</li> 979 * </ul> 980 * </li> 981 * <li>The style is a kind or class style. 982 * <ul> 983 * <li>check all the groups in the kind of the style (graph, node, edge, 984 * sprite) and only in this kind (since other will never be affected).</li> 985 * <li>remove all groups of this kind.</li> 986 * <li>add all elements of this kind anew to recreate the group.</li> 987 * </ul> 988 * </li> 989 * </ol> 990 */ 991 protected void checkForNewStyle(Rule newRule) { 992 switch (newRule.selector.type) { 993 case GRAPH: 994 if (newRule.selector.getId() != null) 995 checkForNewIdStyle(newRule, byGraphIdGroups); 996 else 997 checkForNewStyle(newRule, byGraphIdGroups); 998 break; 999 case NODE: 1000 if (newRule.selector.getId() != null) 1001 checkForNewIdStyle(newRule, byNodeIdGroups); 1002 else 1003 checkForNewStyle(newRule, byNodeIdGroups); 1004 break; 1005 case EDGE: 1006 if (newRule.selector.getId() != null) 1007 checkForNewIdStyle(newRule, byEdgeIdGroups); 1008 else 1009 checkForNewStyle(newRule, byEdgeIdGroups); 1010 break; 1011 case SPRITE: 1012 if (newRule.selector.getId() != null) 1013 checkForNewIdStyle(newRule, bySpriteIdGroups); 1014 else 1015 checkForNewStyle(newRule, bySpriteIdGroups); 1016 break; 1017 case ANY: 1018 default: 1019 throw new RuntimeException("What ?"); 1020 } 1021 } 1022 1023 /** 1024 * Check for a new specific style (applies only to one element). 1025 * 1026 * @param newRule 1027 * The new style rule. 1028 * @param elt2grp 1029 * The name space. 1030 */ 1031 protected void checkForNewIdStyle(Rule newRule, 1032 HashMap<String, String> elt2grp) { 1033 // There is only one element that matches the identifier. 1034 1035 Element element = getElement(newRule.selector.getId(), elt2grp); 1036 1037 if (element != null) { 1038 checkElementStyleGroup(element); 1039 // removeElement( element ); // Remove the element from its old 1040 // group. Potentially delete a group. 1041 // addElement( element ); // Add the element to its new own group 1042 // (since this is an ID style). 1043 } 1044 } 1045 1046 /** 1047 * Check for a new kind or class style in a given name space (node, edge, 1048 * sprite, graph). 1049 * 1050 * @param newRule 1051 * The new style rule. 1052 * @param elt2grp 1053 * The name space. 1054 */ 1055 protected void checkForNewStyle(Rule newRule, 1056 HashMap<String, String> elt2grp) { 1057 ArrayList<Element> elementsToCheck = new ArrayList<Element>(); 1058 1059 for (String eltId : elt2grp.keySet()) 1060 elementsToCheck.add(getElement(eltId, elt2grp)); 1061 1062 for (Element element : elementsToCheck) { 1063 checkElementStyleGroup(element); 1064 // removeElement( element ); 1065 // addElement( element ); 1066 } 1067 } 1068 1069 // Utility 1070 1071 @Override 1072 public String toString() { 1073 StringBuilder builder = new StringBuilder(); 1074 1075 builder.append(String.format("Style groups (%d) :%n", groups.size())); 1076 1077 for (StyleGroup group : groups.values()) { 1078 builder.append(group.toString(1)); 1079 builder.append(String.format("%n")); 1080 } 1081 1082 return builder.toString(); 1083 } 1084 1085 // Inner classes 1086 1087 /** 1088 * Set of events (meta-classes) actually active. 1089 * 1090 * <p> 1091 * The event set contains the set of events actually occurring. This is used 1092 * to select alternate styles. The events actually occurring are in 1093 * precedence order. The last one is the most important. 1094 * </p> 1095 * 1096 * @author Antoine Dutot 1097 */ 1098 public class EventSet { 1099 public ArrayList<String> eventSet = new ArrayList<String>(); 1100 1101 public String events[] = new String[0]; 1102 1103 /** 1104 * Add an event to the set. 1105 * 1106 * @param event 1107 * The event to add. 1108 */ 1109 public void pushEvent(String event) { 1110 eventSet.add(event); 1111 events = eventSet.toArray(events); 1112 } 1113 1114 /** 1115 * Remove an event from the set. 1116 * 1117 * @param event 1118 * The event to remove. 1119 */ 1120 public void popEvent(String event) { 1121 int index = eventSet.lastIndexOf(event); 1122 1123 if (index >= 0) 1124 eventSet.remove(index); 1125 1126 events = eventSet.toArray(events); 1127 } 1128 1129 /** 1130 * The set of events in order, the most important at the end. 1131 * 1132 * @return The event set. 1133 */ 1134 public String[] getEvents() { 1135 return events; 1136 } 1137 } 1138 1139 /** 1140 * All the style groups sorted by their Z index. 1141 * 1142 * <p> 1143 * This structure is maintained by each time a group is added or removed, or 1144 * when the style of a group changed. 1145 * </p> 1146 * 1147 * @author Antoine Dutot 1148 */ 1149 public class ZIndex implements Iterable<HashSet<StyleGroup>> { 1150 /** 1151 * Ordered set of groups. 1152 */ 1153 public ArrayList<HashSet<StyleGroup>> zIndex = new ArrayList<HashSet<StyleGroup>>(); 1154 1155 /** 1156 * Knowing a group, tell if its Z index. 1157 */ 1158 public HashMap<String, Integer> reverseZIndex = new HashMap<String, Integer>(); 1159 1160 /** 1161 * New empty Z index. 1162 */ 1163 public ZIndex() { 1164 initZIndex(); 1165 } 1166 1167 protected void initZIndex() { 1168 zIndex.ensureCapacity(256); 1169 1170 for (int i = 0; i < 256; i++) 1171 zIndex.add(null); 1172 } 1173 1174 /** 1175 * Iterator on the set of Z index cells. Each item is a set of style 1176 * groups that pertain to the same Z index. 1177 * 1178 * @return Iterator on the Z index. 1179 */ 1180 protected Iterator<HashSet<StyleGroup>> getIterator() { 1181 return new ZIndexIterator(); 1182 } 1183 1184 public Iterator<HashSet<StyleGroup>> iterator() { 1185 return getIterator(); 1186 } 1187 1188 /** 1189 * A new group appeared, put it in the z index. 1190 * 1191 * @param group 1192 * The group to add. 1193 */ 1194 protected void groupAdded(StyleGroup group) { 1195 int z = convertZ(group.getZIndex()); 1196 1197 if (zIndex.get(z) == null) 1198 zIndex.set(z, new HashSet<StyleGroup>()); 1199 1200 zIndex.get(z).add(group); 1201 reverseZIndex.put(group.getId(), z); 1202 } 1203 1204 /** 1205 * A group eventually changed, check its location. 1206 * 1207 * @param group 1208 * The group to check. 1209 */ 1210 protected void groupChanged(StyleGroup group) { 1211 int oldZ = reverseZIndex.get(group.getId()); 1212 int newZ = convertZ(group.getZIndex()); 1213 1214 if (oldZ != newZ) { 1215 HashSet<StyleGroup> map = zIndex.get(oldZ); 1216 1217 if (map != null) { 1218 map.remove(group); 1219 reverseZIndex.remove(group.getId()); 1220 1221 if (map.isEmpty()) 1222 zIndex.set(oldZ, null); 1223 } 1224 1225 groupAdded(group); 1226 } 1227 } 1228 1229 /** 1230 * A group was removed, remove it from the Z index. 1231 * 1232 * @param group 1233 * The group to remove. 1234 */ 1235 protected void groupRemoved(StyleGroup group) { 1236 int z = convertZ(group.getZIndex()); 1237 1238 HashSet<StyleGroup> map = zIndex.get(z); 1239 1240 if (map != null) { 1241 map.remove(group); 1242 reverseZIndex.remove(group.getId()); 1243 1244 if (map.isEmpty()) 1245 zIndex.set(z, null); 1246 } else { 1247 throw new RuntimeException("Inconsistency in Z-index"); 1248 } 1249 } 1250 1251 public void clear() { 1252 zIndex.clear(); 1253 reverseZIndex.clear(); 1254 initZIndex(); 1255 } 1256 1257 /** 1258 * Convert a [-127,127] value into a [0,255] value and check bounds. 1259 * 1260 * @param z 1261 * The Z value to convert. 1262 * @return The Z value converted and bounded to [0,255]. 1263 */ 1264 protected int convertZ(int z) { 1265 z += 127; 1266 1267 if (z < 0) 1268 z = 0; 1269 else if (z > 255) 1270 z = 255; 1271 1272 return z; 1273 } 1274 1275 @Override 1276 public String toString() { 1277 StringBuilder sb = new StringBuilder(); 1278 1279 sb.append(String.format("Z index :%n")); 1280 1281 for (int i = 0; i < 256; i++) { 1282 if (zIndex.get(i) != null) { 1283 sb.append(String.format(" * %d -> ", i - 127)); 1284 1285 HashSet<StyleGroup> map = zIndex.get(i); 1286 1287 for (StyleGroup g : map) 1288 sb.append(String.format("%s ", g.getId())); 1289 1290 sb.append(String.format("%n")); 1291 } 1292 } 1293 1294 return sb.toString(); 1295 } 1296 1297 public class ZIndexIterator implements Iterator<HashSet<StyleGroup>> { 1298 public int index = 0; 1299 1300 public ZIndexIterator() { 1301 zapUntilACell(); 1302 } 1303 1304 protected void zapUntilACell() { 1305 while (index < 256 && zIndex.get(index) == null) 1306 index++; 1307 } 1308 1309 public boolean hasNext() { 1310 return (index < 256); 1311 } 1312 1313 public HashSet<StyleGroup> next() { 1314 if (hasNext()) { 1315 HashSet<StyleGroup> cell = zIndex.get(index); 1316 index++; 1317 zapUntilACell(); 1318 return cell; 1319 } 1320 1321 return null; 1322 } 1323 1324 public void remove() { 1325 throw new RuntimeException( 1326 "This iterator does not support removal."); 1327 } 1328 } 1329 } 1330 1331 /** 1332 * Set of groups that cast a shadow. 1333 * 1334 * @author Antoine Dutot 1335 */ 1336 public class ShadowSet implements Iterable<StyleGroup> { 1337 /** 1338 * The set of groups casting shadow. 1339 */ 1340 protected HashSet<StyleGroup> shadowSet = new HashSet<StyleGroup>(); 1341 1342 /** 1343 * Iterator on the set of groups that cast a shadow. 1344 * 1345 * @return An iterator on the shadow style group set. 1346 */ 1347 protected Iterator<StyleGroup> getIterator() { 1348 return shadowSet.iterator(); 1349 } 1350 1351 public Iterator<StyleGroup> iterator() { 1352 return getIterator(); 1353 } 1354 1355 /** 1356 * A group appeared, check its shadow status. 1357 * 1358 * @param group 1359 * The group added. 1360 */ 1361 protected void groupAdded(StyleGroup group) { 1362 if (group.getShadowMode() != ShadowMode.NONE) 1363 shadowSet.add(group); 1364 } 1365 1366 /** 1367 * A group eventually changed, check its shadow status. 1368 * 1369 * @param group 1370 * The group that changed. 1371 */ 1372 protected void groupChanged(StyleGroup group) { 1373 if (group.getShadowMode() == ShadowMode.NONE) 1374 shadowSet.remove(group); 1375 else 1376 shadowSet.add(group); 1377 } 1378 1379 /** 1380 * A group was removed, remove it from the shadow if needed. 1381 * 1382 * @param group 1383 * The group removed. 1384 */ 1385 protected void groupRemoved(StyleGroup group) { 1386 // Faster than to first test its existence or shadow status : 1387 1388 shadowSet.remove(group); 1389 } 1390 1391 protected void clear() { 1392 shadowSet.clear(); 1393 } 1394 } 1395 1396 /** 1397 * Iterator that allows to browse all graph elements of a given kind (nodes, 1398 * edges, sprites, graphs) as if they where in a single set, whereas they 1399 * are in style groups. 1400 * 1401 * @author Antoine Dutot 1402 * @param <E> 1403 * The kind of graph element. 1404 */ 1405 protected class ElementIterator<E extends Element> implements Iterator<E> { 1406 protected HashMap<String, String> elt2grp; 1407 1408 protected Iterator<String> elts; 1409 1410 public ElementIterator(HashMap<String, String> elements2groups) { 1411 elt2grp = elements2groups; 1412 elts = elements2groups.keySet().iterator(); 1413 } 1414 1415 public boolean hasNext() { 1416 return elts.hasNext(); 1417 } 1418 1419 @SuppressWarnings("unchecked") 1420 public E next() { 1421 String eid = elts.next(); 1422 String gid = elt2grp.get(eid); 1423 StyleGroup grp = groups.get(gid); 1424 1425 return (E) grp.getElement(eid); 1426 } 1427 1428 public void remove() { 1429 throw new RuntimeException( 1430 "remove not implemented in this iterator"); 1431 } 1432 } 1433 1434 /** 1435 * Dummy set of nodes. 1436 */ 1437 protected class NodeSet implements Iterable<Node> { 1438 @SuppressWarnings("unchecked") 1439 public Iterator<Node> iterator() { 1440 return (Iterator<Node>) getNodeIterator(); 1441 } 1442 } 1443 1444 /** 1445 * Dummy set of edges. 1446 */ 1447 protected class EdgeSet implements Iterable<Edge> { 1448 @SuppressWarnings("unchecked") 1449 public Iterator<Edge> iterator() { 1450 return (Iterator<Edge>) getEdgeIterator(); 1451 } 1452 } 1453 1454 /** 1455 * Dummy set of sprites. 1456 */ 1457 protected class SpriteSet implements Iterable<GraphicSprite> { 1458 @SuppressWarnings("unchecked") 1459 public Iterator<GraphicSprite> iterator() { 1460 return (Iterator<GraphicSprite>) getSpriteIterator(); 1461 } 1462 } 1463 1464 protected class GraphSet implements Iterable<GraphicGraph> { 1465 @SuppressWarnings("unchecked") 1466 public Iterator<GraphicGraph> iterator() { 1467 return (Iterator<GraphicGraph>) getGraphIterator(); 1468 } 1469 } 1470 1471}