// Copyright 2010 Google Inc. All Rights Reseved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.testing.testify.risk.frontend.client.view.widgets; import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.MouseOutEvent; import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.event.dom.client.MouseOverEvent; import com.google.gwt.event.dom.client.MouseOverHandler; import com.google.gwt.event.logical.shared.HasValueChangeHandlers; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; import com.google.gwt.user.client.ui.Label; import com.google.testing.testify.risk.frontend.model.Attribute; import com.google.testing.testify.risk.frontend.model.Capability; import com.google.testing.testify.risk.frontend.model.Component; import com.google.testing.testify.risk.frontend.model.Pair; import java.util.Collection; import java.util.List; import java.util.Map; /** * A widget for displaying a set of capabilities in a Grid. * * @author jimr@google.com (Jim Reardon) */ public class CapabilitiesGridWidget extends Composite implements HasValueChangeHandlers<Pair<Component, Attribute>> { private final List<Attribute> attributes = Lists.newArrayList(); private final List<Component> components = Lists.newArrayList(); // Map from intersection key to capability list. private final Multimap<Integer, Capability> capabilityMap = HashMultimap.create(); // Map from intersection key to table cell. private final Map<Integer, HTML> cellMap = Maps.newHashMap(); private final Grid grid = new Grid(); private int highlightedCellRow = -1; private int highlightedCellColumn = -1; public CapabilitiesGridWidget() { grid.addStyleName("tty-ComponentAttributeGrid"); grid.addStyleName("tty-CapabilitiesGrid"); initWidget(grid); } /** * Adds a single capability without clearing out prior capabilities. * * @param capability capability to add. */ public void addCapability(Capability capability) { Integer key = capability.getCapabilityIntersectionKey(); // Add the capability. addCapabilityWithoutUpdate(capability); // Update relevant UI field. updateGridCell(key); } public void deleteCapability(Capability capability) { for (Integer key : capabilityMap.keySet()) { Collection<Capability> c = capabilityMap.get(key); if (c.contains(capability)) { c.remove(capability); updateGridCell(key); } } } public void updateCapability(Capability capability) { deleteCapability(capability); addCapability(capability); } public void setAttributes(List<Attribute> attributes) { this.attributes.clear(); this.attributes.addAll(attributes); redrawGrid(); } public void setComponents(List<Component> components) { this.components.clear(); this.components.addAll(components); redrawGrid(); } public void setCapabilities(List<Capability> capabilities) { capabilityMap.clear(); for (Capability capability : capabilities) { addCapabilityWithoutUpdate(capability); } updateGridCells(); } private void addCapabilityWithoutUpdate(Capability capability) { Integer key = capability.getCapabilityIntersectionKey(); capabilityMap.put(key, capability); } /** * Completely recreates the grid then calls updateGridCells() to update contents of grid. * This should be called if the list of components or attributes changes. */ private void redrawGrid() { grid.clear(); cellMap.clear(); if (components.size() < 1 || attributes.size() < 1) { return; } grid.resize(components.size() + 1, attributes.size() + 1); grid.getCellFormatter().setStyleName(0, 0, "tty-GridXHeaderCell"); // Add component headers. for (int i = 0; i < components.size(); i++) { Label label = new Label(components.get(i).getName()); grid.getCellFormatter().setStyleName(i + 1, 0, "tty-GridXHeaderCell"); grid.setWidget(i + 1, 0, label); } // Add attribute headers. for (int i = 0; i < attributes.size(); i++) { Label label = new Label(attributes.get(i).getName()); grid.getCellFormatter().setStyleName(0, i + 1, "tty-GridYHeaderCell"); grid.setWidget(0, i + 1, label); } // Fill up the rest of the grid with labels. for (int cIndex = 0; cIndex < components.size(); cIndex++) { for (int aIndex = 0; aIndex < attributes.size(); aIndex++) { int row = cIndex + 1; int column = aIndex + 1; Component component = components.get(cIndex); Attribute attribute = attributes.get(aIndex); Integer key = Capability.getCapabilityIntersectionKey(component, attribute); grid.getCellFormatter().setStyleName(row, column, "tty-GridCell"); if (row == highlightedCellRow && column == highlightedCellColumn) { grid.getCellFormatter().addStyleName(row, column, "tty-GridCellSelected"); } HTML cell = new HTML(); cell.addClickHandler(cellClickHandler(row, column)); cell.addMouseOverHandler(createMouseOverHandler(row, column)); cell.addMouseOutHandler(createMouseOutHandler(row, column)); cellMap.put(key, cell); grid.setWidget(row, column, cell); } } updateGridCells(); } /** * Creates a mouse over handler for a specific row and column. * * @param row the row number. * @param column the column number. * @return the mouse over handler. */ private MouseOverHandler createMouseOverHandler(final int row, final int column) { return new MouseOverHandler() { @Override public void onMouseOver(MouseOverEvent event) { mouseOver(row, column); } }; } /** * Generates a mouse out handler for a specific row and column. * * @param row the row. * @param column the column. * @return a mouse out handler. */ private MouseOutHandler createMouseOutHandler(final int row, final int column) { return new MouseOutHandler() { @Override public void onMouseOut(MouseOutEvent event) { mouseOut(row, column); } }; } /** * Handles a mouse out event by removing the formatting on cells that were once highlighted due * to a mouse over event. * * @param row the row that lost mouse over. * @param column the column that lost mouse over. */ private void mouseOut(int row, int column) { CellFormatter formatter = grid.getCellFormatter(); // Remove highlighting from cell. formatter.removeStyleName(row, column, "tty-GridCellHighlighted"); // Remove column highlighting. for (int i = 1; i < grid.getRowCount(); i++) { formatter.removeStyleName(i, column, "tty-GridColumnHighlighted"); } // Remove row highlighting. for (int j = 1; j < grid.getColumnCount(); j++) { formatter.removeStyleName(row, j, "tty-GridRowHighlighted"); } } /** * Handles a mouse over event by highlighting the moused over cell and adding style to the * row and column that is also to be highlighted. * * @param row the row that gained mouse over. * @param column the column that gained mouse over. */ private void mouseOver(int row, int column) { CellFormatter formatter = grid.getCellFormatter(); // Add highlighting to cell. formatter.addStyleName(row, column, "tty-GridCellHighlighted"); // Add column highlighting. for (int i = 1; i < grid.getRowCount(); i++) { if (i != row) { formatter.addStyleName(i, column, "tty-GridColumnHighlighted"); } } // Add row highlighting. for (int j = 1; j < grid.getColumnCount(); j++) { if (j != column) { formatter.addStyleName(row, j, "tty-GridRowHighlighted"); } } } /** * This updates the contents of the grid based off the current capability data. */ private void updateGridCells() { for (Integer key : cellMap.keySet()) { updateGridCell(key); } } private void updateGridCell(Integer key) { int size = capabilityMap.get(key).size(); String text = " "; if (size > 0) { text = Integer.toString(size); } cellMap.get(key).setHTML(text); } private ClickHandler cellClickHandler(final int row, final int column) { final HasValueChangeHandlers<Pair<Component, Attribute>> self = this; return new ClickHandler() { @Override public void onClick(ClickEvent event) { cellClicked(row, column); } }; } private void cellClicked(int row, int column) { final Component component = components.get(row - 1); final Attribute attribute = attributes.get(column - 1); if (highlightedCellRow != -1 && highlightedCellColumn != -1) { grid.getCellFormatter().removeStyleName( highlightedCellRow, highlightedCellColumn, "tty-GridCellSelected"); } grid.getCellFormatter().addStyleName(row, column, "tty-GridCellSelected"); highlightedCellRow = row; highlightedCellColumn = column; ValueChangeEvent.fire(this, new Pair<Component, Attribute>(component, attribute)); } @Override public HandlerRegistration addValueChangeHandler( ValueChangeHandler<Pair<Component, Attribute>> handler) { return addHandler(handler, ValueChangeEvent.getType()); } }