// 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.impl;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiFactory;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.testing.testify.risk.frontend.client.event.WidgetsReorderedEvent;
import com.google.testing.testify.risk.frontend.client.event.WidgetsReorderedHandler;
import com.google.testing.testify.risk.frontend.client.view.CapabilitiesView;
import com.google.testing.testify.risk.frontend.client.view.widgets.CapabilitiesGridWidget;
import com.google.testing.testify.risk.frontend.client.view.widgets.EasyDisclosurePanel;
import com.google.testing.testify.risk.frontend.client.view.widgets.EditCapabilityWidget;
import com.google.testing.testify.risk.frontend.client.view.widgets.SortableVerticalPanel;
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;
/**
* Generic view on top of a Project's Capabilities. Note that this View has two alternate View
* methods (List and Grid) both wired to the same Model, View, Presenter setup.
*
* @author chrsmith@google.com (Chris Smith)
*/
public class CapabilitiesViewImpl extends Composite implements CapabilitiesView {
private static final String HEADER_TEXT = "Capabilities by Attribute and Component";
/**
* Used to wire parent class to associated UI Binder.
*/
interface CapabilitiesViewImplUiBinder extends UiBinder<Widget, CapabilitiesViewImpl> {}
private static final CapabilitiesViewImplUiBinder uiBinder =
GWT.create(CapabilitiesViewImplUiBinder.class);
@UiField
public CapabilitiesGridWidget capabilitiesGrid;
@UiField
public VerticalPanel capabilitiesContainer;
@UiField
public Label capabilitiesContainerTitle;
@UiField
public HorizontalPanel addNewCapabilityPanel;
@UiField
public TextBox newCapabilityName;
@UiField
public Button addNewCapabilityButton;
@UiField
public SortableVerticalPanel<EditCapabilityWidget> capabilitiesPanel;
private List<Capability> capabilities;
private List<Component> components;
private List<Attribute> attributes;
private final Collection<String> projectLabels = Lists.newArrayList();
private Pair<Component, Attribute> selectedIntersection;
private boolean isEditable = false;
private Presenter presenter;
/**
* Constructs a CapabilitiesViewImpl object.
*/
public CapabilitiesViewImpl() {
initWidget(uiBinder.createAndBindUi(this));
capabilitiesGrid.addValueChangeHandler(new ValueChangeHandler<Pair<Component,Attribute>>() {
@Override
public void onValueChange(ValueChangeEvent<Pair<Component, Attribute>> event) {
selectedIntersection = event.getValue();
newCapabilityName.setText("");
tryToPopulateCapabilitiesPanel();
}
});
capabilitiesPanel.addWidgetsReorderedHandler(
new WidgetsReorderedHandler() {
@Override
public void onWidgetsReordered(WidgetsReorderedEvent event) {
if (presenter != null) {
List<Long> ids = Lists.newArrayList();
for (Widget w : event.getWidgetOrdering()) {
ids.add(((EditCapabilityWidget) w).getCapabilityId());
}
presenter.reorderCapabilities(ids);
}
}
});
newCapabilityName.getElement().setAttribute("placeholder", "Add new capability...");
}
@UiFactory
public EasyDisclosurePanel createDisclosurePanel() {
Label header = new Label(HEADER_TEXT);
header.addStyleName("tty-DisclosureHeader");
return new EasyDisclosurePanel(header);
}
/**
* Handler for hitting enter in the new capability text box.
*/
@UiHandler("newCapabilityName")
void onComponentNameEnter(KeyDownEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
addNewCapabilityButton.click();
}
}
@UiHandler("addNewCapabilityButton")
protected void handleNewButton(ClickEvent e) {
String name = newCapabilityName.getText().trim();
if (name.length() == 0) {
Window.alert("Please enter a name for the capability.");
return;
}
long cId = selectedIntersection.getFirst().getComponentId();
long aId = selectedIntersection.getSecond().getAttributeId();
long pId = selectedIntersection.getSecond().getParentProjectId();
Capability c = new Capability(pId, aId, cId);
c.setName(name);
presenter.onAddCapability(c);
newCapabilityName.setText("");
}
@Override
public Widget asWidget() {
return this;
}
@Override
public void setPresenter(Presenter presenter) {
this.presenter = presenter;
}
@Override
public void setCapabilities(List<Capability> capabilities) {
this.capabilities = capabilities;
capabilitiesGrid.setCapabilities(capabilities);
tryToPopulateCapabilitiesPanel();
}
@Override
public void setAttributes(List<Attribute> attributes) {
this.attributes = attributes;
capabilitiesGrid.setAttributes(attributes);
tryToPopulateCapabilitiesPanel();
}
@Override
public void setComponents(List<Component> components) {
this.components = components;
capabilitiesGrid.setComponents(components);
tryToPopulateCapabilitiesPanel();
}
@Override
public void setProjectLabels(Collection<String> labels) {
projectLabels.clear();
projectLabels.addAll(labels);
for (Widget w : capabilitiesContainer) {
EditCapabilityWidget capabilityWidget = (EditCapabilityWidget) w;
capabilityWidget.setLabelSuggestions(projectLabels);
}
}
private void updateCapability(Capability capability) {
presenter.onUpdateCapability(capability);
capabilitiesGrid.updateCapability(capability);
tryToPopulateCapabilitiesPanel();
}
private void removeCapability(Capability capability) {
presenter.onRemoveCapability(capability);
capabilitiesGrid.deleteCapability(capability);
capabilities.remove(capability);
tryToPopulateCapabilitiesPanel();
}
/**
* Populates the capabilities panel with capabilities widgets. This requires data already be
* loaded, so it will not show the panel if all data has not been loaded.
*/
private void tryToPopulateCapabilitiesPanel() {
if (selectedIntersection != null && attributes != null && components != null
&& capabilities != null) {
Component component = selectedIntersection.getFirst();
Attribute attribute = selectedIntersection.getSecond();
capabilitiesContainer.setVisible(true);
capabilitiesContainerTitle.setText(component.getName() + " is " + attribute.getName());
List<EditCapabilityWidget> widgets = Lists.newArrayList();
for (final Capability capability : capabilities) {
// If we're interested in this capability (it matches our current filter).
if (capability.getComponentId() == component.getComponentId()
&& capability.getAttributeId() == attribute.getAttributeId()) {
// Create and populate a capability widget for this capability.
final EditCapabilityWidget widget = new EditCapabilityWidget(capability);
widget.addValueChangeHandler(new ValueChangeHandler<Capability>() {
@Override
public void onValueChange(ValueChangeEvent<Capability> event) {
if (event.getValue() == null) {
// Since the value is null, we'll just use the old value to grab the ID.
removeCapability(capability);
} else {
updateCapability(event.getValue());
widget.showSaved();
}
}
});
widget.setComponents(components);
widget.setAttributes(attributes);
widget.setLabelSuggestions(projectLabels);
if (isEditable) {
widget.makeEditable();
}
widgets.add(widget);
}
}
capabilitiesPanel.setWidgets(widgets, new Function<EditCapabilityWidget, Widget>() {
@Override
public Widget apply(EditCapabilityWidget input) {
return input.getCapabilityGripper();
}
});
} else {
capabilitiesContainer.setVisible(false);
}
}
@Override
public void setEditable(boolean isEditable) {
this.isEditable = isEditable;
for (Widget w : capabilitiesPanel) {
if (isEditable) {
((EditCapabilityWidget) w).makeEditable();
}
}
addNewCapabilityPanel.setVisible(isEditable);
}
@Override
public void addCapability(Capability capability) {
capabilitiesGrid.addCapability(capability);
// Insert at top.
capabilities.add(0, capability);
// TODO (jimr): instead of a full refresh, we should just pop the new widget
// on top. However, the sortable panel doesn't really handle adding a single
// widget, so a full refresh is the path of least resistance currently.
tryToPopulateCapabilitiesPanel();
}
}