/*
* Copyright 2014-2015 CyberVision, Inc.
*
* 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 org.kaaproject.avro.ui.gwt.client.widget;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.kaaproject.avro.ui.gwt.client.AvroUiResources.AvroUiStyle;
import org.kaaproject.avro.ui.gwt.client.util.Utils;
import org.kaaproject.avro.ui.gwt.client.widget.grid.AbstractGrid;
import org.kaaproject.avro.ui.gwt.client.widget.grid.event.RowActionEvent;
import org.kaaproject.avro.ui.gwt.client.widget.grid.event.RowActionEventHandler;
import org.kaaproject.avro.ui.gwt.client.widget.nav.NavigationActionListener;
import org.kaaproject.avro.ui.gwt.client.widget.nav.NavigationContainer;
import org.kaaproject.avro.ui.shared.ArrayField;
import org.kaaproject.avro.ui.shared.BooleanField;
import org.kaaproject.avro.ui.shared.FieldType;
import org.kaaproject.avro.ui.shared.FormField;
import org.kaaproject.avro.ui.shared.FormField.FieldAccess;
import org.kaaproject.avro.ui.shared.Fqn;
import org.kaaproject.avro.ui.shared.FqnKey;
import org.kaaproject.avro.ui.shared.RecordField;
import org.kaaproject.avro.ui.shared.UnionField;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
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.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.DataGrid;
import com.google.gwt.user.cellview.client.Header;
import com.google.gwt.user.cellview.client.SafeHtmlHeader;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.AsyncDataProvider;
import com.google.gwt.view.client.HasData;
public class ArrayFieldWidget extends AbstractFieldWidget<ArrayField> {
private ArrayGrid arrayGrid;
private ScrollPanel tableScroll;
private FieldWidgetPanel fieldWidgetPanel;
private static final String PX = "px";
public ArrayFieldWidget(AvroWidgetsConfig config, NavigationContainer container, boolean readOnly) {
super(config, container, readOnly);
}
public ArrayFieldWidget(AvroWidgetsConfig config, AvroUiStyle style, NavigationContainer container, boolean readOnly) {
super(config, style, container, readOnly);
}
@Override
protected Widget constructForm() {
fieldWidgetPanel = new FieldWidgetPanel(style, value, readOnly, true);
if (value.isOverride()) {
fieldWidgetPanel.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
if (!readOnly && !value.isReadOnly()) {
fireChanged();
}
if (event.getValue()) {
onShown();
}
}
});
fieldWidgetPanel.setLegendNotes(Utils.constants.getString(value
.getOverrideStrategy().name().toLowerCase()
+ "Strategy"));
}
fieldWidgetPanel.setWidth(config.getArrayPanelWidth());
value.finalizeMetadata();
if (isGridNeeded(value)) {
fieldWidgetPanel.setContent(constructGrid());
} else {
fieldWidgetPanel.setContent(constructTable());
}
return fieldWidgetPanel;
}
@Override
public void updateConfig(AvroWidgetsConfig config) {
super.updateConfig(config);
if (fieldWidgetPanel != null) {
fieldWidgetPanel.setWidth(config.getArrayPanelWidth());
}
if (arrayGrid != null) {
arrayGrid.setHeight(config.getGridHeight());
}
if (tableScroll != null) {
tableScroll.setHeight(config.getTableHeight());
tableScroll.setWidth(getScrollTablePreferredWidth(config.getArrayPanelWidthPx()));
tableScroll.getElement().getStyle().setMargin(AbstractGrid.DEFAULT_GRID_MARGIN, Unit.PX);
}
}
public static boolean isGridNeeded(ArrayField field) {
FormField metadata = field.getElementMetadata();
if (metadata.getFieldType().isComplex()) {
if (metadata.getFieldType() == FieldType.RECORD) {
RecordField recordMetadata = (RecordField)metadata;
if (!recordMetadata.getKeyIndexedFields().isEmpty()) {
return true;
} else {
return hasComplexFields(recordMetadata.getFieldsWithAccess(FieldAccess.EDITABLE,
FieldAccess.READ_ONLY));
}
} else {
return true;
}
} else {
return false;
}
}
private static boolean hasComplexFields(List<FormField> metaFields) {
for (FormField metaField : metaFields) {
if (metaField.getFieldType().isComplex()) {
return true;
}
}
return false;
}
@Override
public void onShown() {
super.onShown();
if (arrayGrid != null) {
arrayGrid.reload();
}
}
private Widget constructGrid() {
VerticalPanel verticalPanel = new VerticalPanel();
verticalPanel.setWidth(FULL_WIDTH);
arrayGrid = new ArrayGrid(value, !readOnly && !value.isReadOnly());
arrayGrid.setHeight(config.getGridHeight());
verticalPanel.add(arrayGrid);
HorizontalPanel buttonsPanel = new HorizontalPanel();
buttonsPanel.addStyleName(style.buttonsPanel());
if (!readOnly && !value.isReadOnly()) {
Button addRow = new Button(Utils.constants.add());
addRow.addStyleName(style.buttonSmall());
addRow.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
FormField newField = value.createRow();
navigationContainer.addNewField(newField, new NavigationActionListener() {
@Override
public void onChanged(FormField field) {}
@Override
public void onAdded(FormField field) {
arrayGrid.getDataProvider().addRow(field);
}
});
}
});
buttonsPanel.add(addRow);
}
arrayGrid.addRowActionHandler(new RowActionEventHandler<Integer>() {
@Override
public void onRowAction(RowActionEvent<Integer> event) {
final int index = event.getClickedId();
if (event.getAction() == RowActionEvent.CLICK) {
FormField field = arrayGrid.getDataProvider().getData().get(index);
navigationContainer.showField(field, null);
} else if (event.getAction() == RowActionEvent.DELETE) {
arrayGrid.getDataProvider().removeRow(index);
fireChanged();
}
}
});
verticalPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
verticalPanel.add(buttonsPanel);
return verticalPanel;
}
private Widget constructTable() {
VerticalPanel verticalPanel = new VerticalPanel();
tableScroll = new ScrollPanel();
final FlexTable table = new FlexTable();
table.setWidth("95%");
table.setCellPadding(0);
table.setCellSpacing(0);
table.addStyleName(style.arrayTable());
List<FormField> records = value.getValue();
final FormField elementMetadata = value.getElementMetadata();
final boolean hasHeader;
if (elementMetadata.getFieldType() == FieldType.RECORD) {
RecordField recordElementData = (RecordField)elementMetadata;
float totalWeight = 0f;
List<FormField> metaFields = recordElementData.getFieldsWithAccess(FieldAccess.EDITABLE,
FieldAccess.READ_ONLY);
for (int column=0;column<metaFields.size();column++) {
FormField metaField = metaFields.get(column);
totalWeight += metaField.getWeight();
}
for (int column=0;column<metaFields.size();column++) {
FormField metaField = metaFields.get(column);
float weight = metaField.getWeight();
String width = String.valueOf(weight/totalWeight*100f)+"%";
table.getColumnFormatter().setWidth(column, width);
table.setWidget(0, column, new Label(metaField.getDisplayName()));
}
if (!readOnly) {
table.setWidget(0, table.getCellCount(0), new Label(Utils.constants.delete()));
}
hasHeader = true;
} else {
hasHeader = false;
}
final Map<FormField, List<HandlerRegistration>> rowHandlerRegistrationMap =
new HashMap<>();
for (int row=0;row<records.size();row++) {
FormField record = records.get(row);
List<HandlerRegistration> rowHandlerRegistrations = new ArrayList<>();
setRow(table, record, row, rowHandlerRegistrations, rowHandlerRegistrationMap, hasHeader);
registrations.addAll(rowHandlerRegistrations);
rowHandlerRegistrationMap.put(record, rowHandlerRegistrations);
}
tableScroll.setWidth(getScrollTablePreferredWidth(config.getArrayPanelWidthPx()));
tableScroll.getElement().getStyle().setMargin(AbstractGrid.DEFAULT_GRID_MARGIN, Unit.PX);
tableScroll.setHeight(config.getTableHeight());
tableScroll.add(table);
verticalPanel.setWidth(FULL_WIDTH);
verticalPanel.add(tableScroll);
if (!readOnly && !value.isReadOnly()) {
Button addRow = new Button(Utils.constants.add());
addRow.addStyleName(style.buttonSmall());
addRow.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
FormField newField = value.createRow();
value.addArrayData(newField);
List<HandlerRegistration> rowHandlerRegistrations = new ArrayList<>();
setRow(table, newField, value.getValue().size() - 1, rowHandlerRegistrations, rowHandlerRegistrationMap, hasHeader);
rowHandlerRegistrationMap.put(newField, rowHandlerRegistrations);
fireChanged();
}
});
HorizontalPanel buttonsPanel = new HorizontalPanel();
buttonsPanel.addStyleName(style.buttonsPanel());
buttonsPanel.add(addRow);
verticalPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
verticalPanel.add(buttonsPanel);
}
return verticalPanel;
}
private void setRow(final FlexTable table, FormField field, int row, List<HandlerRegistration> handlerRegistrations,
final Map<FormField, List<HandlerRegistration>> rowHandlerRegistrationMap, final boolean hasHeader) {
if (hasHeader) {
row++;
}
if (field.getFieldType() == FieldType.RECORD) {
RecordField record = (RecordField)field;
List<FormField> recordFields = record.getFieldsWithAccess(FieldAccess.EDITABLE,
FieldAccess.READ_ONLY);
for (int column=0;column<recordFields.size();column++) {
FormField cellField = recordFields.get(column);
constructAndPlaceWidget(table, cellField, row, column, handlerRegistrations);
}
} else {
constructAndPlaceWidget(table, field, row, 0, handlerRegistrations);
}
if (!readOnly) {
final Button delButton = new Button("");
Image img = new Image(Utils.resources.remove());
img.getElement().getStyle().setVerticalAlign(Style.VerticalAlign.MIDDLE);
delButton.getElement().appendChild(img.getElement());
delButton.addStyleName(style.cellButton());
delButton.addStyleName(style.cellButtonSmall());
delButton.getElement().getStyle().setMarginLeft(3, Unit.PX);
HandlerRegistration handlerRegistration = delButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
int tableRow = table.getCellForEvent(event).getRowIndex();
int rowIndex = hasHeader ? tableRow - 1 : tableRow;
FormField toDelete = value.getValue().get(rowIndex);
List<HandlerRegistration> registrations = rowHandlerRegistrationMap.remove(toDelete);
if (registrations != null) {
for (HandlerRegistration registration : registrations) {
registration.removeHandler();
}
registrations.clear();
}
table.removeRow(tableRow);
value.getValue().remove(rowIndex);
fireChanged();
}
});
handlerRegistrations.add(handlerRegistration);
table.setWidget(row, table.getCellCount(row), delButton);
}
}
private String getScrollTablePreferredWidth(int configWidth) {
return (configWidth - AbstractGrid.DEFAULT_GRID_MARGIN*2) + PX;
}
private static class ArrayGrid extends AbstractGrid<FormField, Integer> {
private static final int MAX_CELL_STRING_LENGTH = 200;
protected static final int DELETE_COLUMN_WIDTH = 70;
private List<FormField> metadata;
private ArrayDataProvider dataProvider;
public ArrayGrid(ArrayField arrayField, boolean enableActions) {
super(Unit.PX, enableActions, true, false);
this.dataProvider = new ArrayDataProvider(arrayField);
FormField elementMetadata = arrayField.getElementMetadata();
if (elementMetadata.getFieldType() == FieldType.RECORD) {
RecordField recordElementMetadata = (RecordField)elementMetadata;
this.metadata = recordElementMetadata.getKeyIndexedFields();
if (this.metadata.isEmpty()) {
this.metadata = ((RecordField)elementMetadata).getFieldsWithAccess(FieldAccess.EDITABLE,
FieldAccess.READ_ONLY);
}
} else if (elementMetadata.getFieldType() == FieldType.UNION && !elementMetadata.isOptional()) {
UnionField unionElementMetadata = (UnionField)elementMetadata;
List<FormField> acceptableValues = unionElementMetadata.getAcceptableValues();
boolean useFirstRecord = true;
List<FormField> keyIndexedFields = null;
for (FormField acceptableValue : acceptableValues) {
if (acceptableValue != null && acceptableValue instanceof RecordField) {
List<FormField> recordKeyIndexedFields = ((RecordField)acceptableValue).getKeyIndexedFields();
if (recordKeyIndexedFields != null && !recordKeyIndexedFields.isEmpty()) {
if (keyIndexedFields == null) {
keyIndexedFields = recordKeyIndexedFields;
} else {
if (keyIndexedFields.size() != recordKeyIndexedFields.size()) {
useFirstRecord = false;
break;
} else {
for (int i=0;i<recordKeyIndexedFields.size();i++) {
if (!keyIndexedFields.get(i).getFieldName().equals(recordKeyIndexedFields.get(i).getFieldName())) {
useFirstRecord = false;
break;
}
}
}
}
} else {
useFirstRecord = false;
break;
}
} else {
useFirstRecord = false;
break;
}
}
if (useFirstRecord && keyIndexedFields != null) {
this.metadata = keyIndexedFields;
}
}
if (this.metadata == null) {
this.metadata = new ArrayList<>();
this.metadata.add(elementMetadata);
}
init();
showShadow(false);
this.dataProvider.addDataDisplay(getDataGrid());
}
public void reload() {
this.dataProvider.reload(getDataGrid());
}
public ArrayDataProvider getDataProvider() {
return this.dataProvider;
}
@Override
protected float constructColumnsImpl(DataGrid<FormField> table) {
float prefWidth = 0;
for (final FormField metaField : metadata) {
float width = 200 * metaField.getWeight();
if (metaField.getFieldType() == FieldType.BOOLEAN) {
prefWidth += constructBooleanColumn(table, metaField.getDisplayName(),
new BooleanValueProvider<FormField>() {
@Override
public Boolean getValue(FormField item) {
FormField field = item;
if (field.getFieldType() == FieldType.RECORD) {
field = ((RecordField)item).getFieldByName(metaField.getFieldName());
}
return ((BooleanField)field).getValue();
}
}, width);
} else if (!metaField.getFieldType().isComplex()) {
prefWidth += constructStringColumn(table, metaField.getDisplayName(),
new StringValueProvider<FormField>() {
@Override
public String getValue(FormField item) {
FormField field = item;
if (field.getFieldType() == FieldType.RECORD) {
field = ((RecordField)item).getFieldByName(metaField.getFieldName());
} else if (field.getFieldType() == FieldType.UNION) {
FormField unionVal = ((UnionField)field).getValue();
field = ((RecordField)unionVal).getFieldByName(metaField.getFieldName());
}
String value = extractStringValue(field);
if (value != null && value.length() > MAX_CELL_STRING_LENGTH) {
value = value.substring(0, MAX_CELL_STRING_LENGTH-3) + "...";
}
return value;
}
}, width);
} else {
prefWidth += constructStringColumn(table, metaField.getDisplayName(),
new StringValueProvider<FormField>() {
@Override
public String getValue(FormField item) {
FormField field = item;
if (field.getFieldType() == FieldType.RECORD) {
field = ((RecordField)item).getFieldByName(metaField.getFieldName());
}
String value = "";
if (metadata.size()==1) {
int index = getObjectId(item);
value = "#" + index + " ";
}
if (field.getFieldType() == FieldType.UNION) {
FormField unionVal = ((UnionField)field).getValue();
if (unionVal == null) {
value += "null";
} else {
if (unionVal.getFieldType() == FieldType.RECORD) {
RecordField recordField = ((RecordField)unionVal);
if (recordField.isTypeConsumer()) {
String fqnString = "Invalid FQN reference";
FqnKey fqnKey = recordField.getConsumedFqnKey();
if (fqnKey != null) {
Fqn fqn = unionVal.getContext().getDeclaredTypes().get(fqnKey);
if (fqn != null) {
fqnString = fqn.getFqnString();
}
}
value += fqnString;
} else if (recordField.isTypeHolder()) {
String fqnString = "Invalid FQN";
Fqn fqn = recordField.getDeclaredFqn();
if (fqn != null) {
fqnString = fqn.getFqnString();
}
value += fqnString;
} else {
value += unionVal.getDisplayName();
}
} else {
value += unionVal.getDisplayName();
}
}
} else {
value += field.getFieldType().getDisplayName();
}
if (value.length() > MAX_CELL_STRING_LENGTH) {
value = value.substring(0, MAX_CELL_STRING_LENGTH-3) + "...";
}
return value;
}
}, width);
}
}
return prefWidth;
}
@Override
protected float constructActions(DataGrid<FormField> table, float prefWidth) {
if (enableActions) {
if (deleteColumn == null || table.getColumnIndex(deleteColumn) == -1) {
Header<SafeHtml> deleteHeader = new SafeHtmlHeader(
SafeHtmlUtils.fromSafeConstant(Utils.constants.delete()));
deleteColumn = constructDeleteColumn("");
table.addColumn(deleteColumn, deleteHeader);
table.setColumnWidth(deleteColumn, DELETE_COLUMN_WIDTH, Unit.PX);
return DELETE_COLUMN_WIDTH;
}
else {
return 0;
}
}
else {
return 0;
}
}
protected Integer getObjectId(FormField value) {
return dataProvider.getData().indexOf(value);
}
}
private static class ArrayDataProvider extends AsyncDataProvider<FormField>{
private ArrayField arrayField;
private boolean loaded = false;
public ArrayDataProvider(ArrayField arrayField) {
this.arrayField = arrayField;
}
public void addRow(FormField row) {
arrayField.addArrayData(row);
updateRowCount(arrayField.getValue().size(), true);
updateRowData(arrayField.getValue().size()-1,
arrayField.getValue().subList(arrayField.getValue().size()-1,
arrayField.getValue().size()));
}
public void removeRow(int index) {
arrayField.removeRow(index);
updateRowCount(arrayField.getValue().size(), true);
int updateIndex = index;
updateIndex = Math.min(updateIndex, arrayField.getValue().size()-1);
updateIndex = Math.max(0, updateIndex);
updateRowData(updateIndex,
arrayField.getValue().subList(updateIndex,
arrayField.getValue().size()));
}
public List<FormField> getData() {
return arrayField.getValue();
}
public void reload(HasData<FormField> display) {
this.loaded = false;
loadData(display);
}
@Override
protected void onRangeChanged(final HasData<FormField> display) {
if (!loaded) {
loadData(display);
}
else {
updateData(display);
}
}
private void loadData(HasData<FormField> display) {
updateRowCount(arrayField.getValue().size(), true);
updateData(display);
loaded = true;
}
private void updateData (HasData<FormField> display) {
int start = display.getVisibleRange().getStart();
int end = start + display.getVisibleRange().getLength();
end = end >= arrayField.getValue().size() ? arrayField.getValue().size() : end;
List<FormField> sub = arrayField.getValue().subList(start, end);
updateRowData(start, sub);
}
}
}