package org.ovirt.engine.ui.common.widget.tree;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.ovirt.engine.core.common.businessentities.BusinessEntity;
import org.ovirt.engine.ui.common.utils.ElementTooltipUtils;
import org.ovirt.engine.ui.common.widget.editor.EntityModelCellTable;
import org.ovirt.engine.ui.common.widget.label.StringValueLabel;
import org.ovirt.engine.ui.common.widget.tooltip.WidgetTooltip;
import org.ovirt.engine.ui.uicommonweb.models.EntityModel;
import org.ovirt.engine.ui.uicommonweb.models.ListModel;
import org.ovirt.engine.ui.uicommonweb.models.SearchableListModel;
import org.ovirt.engine.ui.uicompat.EventArgs;
import org.ovirt.engine.ui.uicompat.IEventListener;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.logical.shared.OpenHandler;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.CellTable;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.ValueLabel;
import com.google.gwt.user.client.ui.Widget;
public abstract class AbstractSubTabTree<M extends SearchableListModel, R, N> extends Composite {
protected final Tree tree;
protected Map<Object, Boolean> oldItemStatesMap;
protected Map<Object, TreeItem> oldRootItemsMap;
protected ArrayList<Object> selectedItems;
protected ArrayList<Object> newSelectedItems;
protected M listModel;
protected boolean isRootSelectionEnabled;
protected boolean isNodeSelectionEnabled;
protected boolean isMultiSelection;
protected boolean isControlKeyDown;
protected static final String NODE_HEADER = "nodeHeader"; //$NON-NLS-1$
public AbstractSubTabTree() {
tree = new Tree();
initWidget(tree);
selectedItems = new ArrayList<>();
newSelectedItems = new ArrayList<>();
oldRootItemsMap = new HashMap<>();
oldItemStatesMap = new HashMap<>();
isMultiSelection = true;
tree.addOpenHandler(treeOpenHandler);
addSelectionHandler();
}
public boolean isMultiSelection() {
return isMultiSelection;
}
public void setMultiSelection(boolean isMultiSelection) {
this.isMultiSelection = isMultiSelection;
}
public void setRootSelectionEnabled(boolean isRootSelectionEnabled) {
this.isRootSelectionEnabled = isRootSelectionEnabled;
selectedItems.clear();
}
public void setNodeSelectionEnabled(boolean isNodeSelectionEnabled) {
this.isNodeSelectionEnabled = isNodeSelectionEnabled;
selectedItems.clear();
}
private IEventListener<EventArgs> itemsChangedEventListener = (ev, sender, args) -> refreshTree();
private OpenHandler<TreeItem> treeOpenHandler = event -> {
TreeItem item = event.getTarget();
onTreeItemOpen(item);
};
public void clearTree() {
tree.clear();
}
private void saveTreeState() {
oldItemStatesMap.clear();
for (int i = 0; i < tree.getItemCount(); i++) {
TreeItem rootItem = tree.getItem(i);
oldRootItemsMap.put(rootItem.getUserObject(), rootItem);
saveTreeItemState(rootItem);
}
}
private void saveTreeItemState(TreeItem item) {
oldItemStatesMap.put(item.getUserObject(), item.getState());
for (int n = 0; n < item.getChildCount(); n++) {
saveTreeItemState(item.getChild(n));
}
}
private void updateTreeState() {
newSelectedItems.clear();
for (int i = 0; i < tree.getItemCount(); i++) {
updateItemSelection(tree.getItem(i));
}
selectedItems.clear();
selectedItems.addAll(newSelectedItems);
onItemsSelection();
}
private boolean getItemOldState(Object userObject) {
for (Map.Entry<Object, Boolean> oldItemStatesEntry : oldItemStatesMap.entrySet()) {
if (oldItemStatesEntry.getKey() != null && userObject != null && oldItemStatesEntry.getKey().equals(userObject)) {
return oldItemStatesEntry.getValue();
}
}
return false;
}
public void updateTree(M listModel) {
this.listModel = listModel;
selectedItems.clear();
onItemsSelection();
listModel.getItemsChangedEvent().removeListener(itemsChangedEventListener);
listModel.getItemsChangedEvent().addListener(itemsChangedEventListener);
if (listModel.getItems() != null) {
refreshTree();
}
}
public void refreshTree() {
List<R> rootItems = (List<R>) listModel.getItems();
saveTreeState();
tree.clear();
if (rootItems == null) {
return;
}
for (R root : rootItems) {
TreeItem rootItem = getRootItem(root);
TreeItem nodeHeader = getNodeHeader();
if (nodeHeader != null) {
rootItem.addItem(nodeHeader);
}
if (getNodeObjects(root).isEmpty()) {
boolean isOpen = getItemOldState(rootItem.getUserObject());
if (isOpen) {
rootItem = oldRootItemsMap.get(rootItem.getUserObject());
}
else {
if (getEmptyRoot() != null) {
rootItem.addItem(getEmptyRoot());
}
}
} else {
for (N node : getNodeObjects(root)) {
TreeItem nodeItem = getNodeItem(node);
addLeaves(nodeItem, node);
rootItem.addItem(nodeItem);
styleItem(nodeItem, getIsNodeEnabled(node));
}
}
tree.addItem(rootItem);
styleItem(rootItem, true);
}
updateTreeState();
}
protected void addLeaves(TreeItem nodeItem, N node) {
TreeItem leafItem = getLeafItem(node);
if (leafItem != null) {
nodeItem.addItem(leafItem);
styleItem(leafItem, getIsNodeEnabled(node));
}
}
protected abstract TreeItem getRootItem(R rootObject);
protected abstract TreeItem getNodeItem(N nodeObject);
protected TreeItem getLeafItem(N nodeObject) {
return null;
}
protected TreeItem getNodeHeader() {
return null;
}
protected TreeItem getEmptyRoot() {
return null;
}
protected abstract List<N> getNodeObjects(R root);
protected boolean getIsNodeEnabled(N nodeObject) {
return true;
}
protected String getNodeDisabledTooltip() {
return null;
}
protected void onTreeItemOpen(TreeItem item) {
}
public interface TreeHeaderlessTableResources extends CellTable.Resources {
interface TableStyle extends CellTable.Style {
}
@Override
@Source({ CellTable.Style.DEFAULT_CSS, "org/ovirt/engine/ui/common/css/TreeHeaderlessTable.css" })
TableStyle cellTableStyle();
}
protected TreeItem createTreeItem(EntityModelCellTable<ListModel> table, ArrayList<EntityModel> list) {
table.setRowData(list);
table.setWidth("100%"); //$NON-NLS-1$
TreeItem item = new TreeItem(table);
return item;
}
protected void styleItem(TreeItem item, boolean enabled) {
Element tableElm = DOM.getFirstChild(item.getElement());
tableElm.setAttribute("width", "100%"); //$NON-NLS-1$ //$NON-NLS-2$
Element col = tableElm.getElementsByTagName("td").getItem(0); //$NON-NLS-1$
col.setAttribute("width", "20px"); //$NON-NLS-1$ //$NON-NLS-2$
NodeList<Element> inputs = item.getElement().getElementsByTagName("input"); //$NON-NLS-1$
for (int i = 0; i < inputs.getLength(); i++) {
if (!enabled) {
disableElement(inputs.getItem(i));
} else {
ElementTooltipUtils.destroyTooltip(inputs.getItem(i));
}
}
NodeList<Element> spans = item.getElement().getElementsByTagName("span"); //$NON-NLS-1$
for (int i = 0; i < spans.getLength(); i++) {
if (!enabled) {
disableElement(spans.getItem(i));
} else {
ElementTooltipUtils.destroyTooltip(spans.getItem(i));
}
}
boolean isLeafEmpty = item.getUserObject() != null && item.getUserObject().equals(true);
if (isLeafEmpty) {
item.getElement().getElementsByTagName("td").getItem(0).getStyle().setVisibility(Visibility.HIDDEN); //$NON-NLS-1$
}
}
protected void addTextBoxToPanel(HorizontalPanel panel, StringValueLabel item, String text, String width) {
item.setValue(text);
addItemToPanel(panel, item, width);
}
protected void addTextBoxToPanel(HorizontalPanel panel, WidgetTooltip item, String text, String width) {
Widget w = item.getWidget();
if (w instanceof Label) {
Label label = (Label) item.getWidget();
label.setText(text);
addItemToPanel(panel, item, width);
}
else if (w instanceof StringValueLabel) {
StringValueLabel label = (StringValueLabel) item.getWidget();
label.setValue(text);
addItemToPanel(panel, item, width);
}
else {
throw new ClassCastException("tooltipped label contains unknown Widget: " + w.getClass()); //$NON-NLS-1$
}
}
protected <T> void addValueLabelToPanel(HorizontalPanel panel, ValueLabel<T> item, T value, String width) {
item.setValue(value);
addItemToPanel(panel, item, width);
}
protected void addItemToPanel(HorizontalPanel panel, IsWidget item, String width) {
addItemToPanel(panel, item.asWidget(), width);
}
protected void addItemToPanel(HorizontalPanel panel, Widget item, String width) {
item.getElement().getStyle().setBackgroundColor("transparent"); //$NON-NLS-1$
item.getElement().getStyle().setColor("black"); //$NON-NLS-1$
panel.add(item);
panel.setCellWidth(item, width);
}
protected void disableElement(Element element) {
element.getStyle().setProperty("disabled", "true"); //$NON-NLS-1$ //$NON-NLS-2$
element.getStyle().setColor("#999999"); //$NON-NLS-1$
if (getNodeDisabledTooltip() != null) {
ElementTooltipUtils.setTooltipOnElement(element, SafeHtmlUtils.fromString(getNodeDisabledTooltip()));
}
}
public void addSelectionHandler() {
tree.addSelectionHandler(event -> onItemSelection(event.getSelectedItem(), false));
tree.addMouseDownHandler(event -> {
if (event.getNativeEvent().getButton() == NativeEvent.BUTTON_RIGHT) {
onItemSelection(findSelectedItem(event.getClientX(), event.getClientY()), true);
}
});
tree.addKeyDownHandler(event -> isControlKeyDown = event.isControlKeyDown());
tree.addKeyUpHandler(event -> isControlKeyDown = event.isControlKeyDown());
}
private void onItemSelection(TreeItem item, boolean enforceSelection) {
Object entity = item.getUserObject();
boolean isRootItem = item.getParentItem() == null;
boolean isNodeItem = item.getParentItem() != null;
if ((isRootItem && !isRootSelectionEnabled) || (isNodeItem && !isNodeSelectionEnabled)) {
return;
}
if (!isControlKeyDown || !isMultiSelection) {
selectedItems.clear();
}
saveTreeState();
updateTreeState();
if (!selectedItems.contains(entity)) {
selectedItems.add(entity);
onItemsSelection();
}
else if (!enforceSelection) {
selectedItems.remove(entity);
onItemsSelection();
}
updateItemSelection(item);
}
private void updateItemSelection(TreeItem item) {
Object entity = item.getUserObject();
boolean isRootItem = item.getParentItem() == null;
if (entity == null) {
return;
}
// Update selected Items
boolean selected = false;
for (Object selectedEntity : selectedItems) {
if (entity.equals(selectedEntity) && !newSelectedItems.contains(selectedEntity)) {
selected = true;
newSelectedItems.add(selectedEntity);
}
}
// Update element's style
Element element = isRootItem ?
item.getElement().getElementsByTagName("table").getItem(0) : item.getElement(); //$NON-NLS-1$
if (!NODE_HEADER.equals(item.getUserObject())) {
element.getStyle().setBackgroundColor(selected ? "#C3D0E0" : "transparent"); //$NON-NLS-1$ //$NON-NLS-2$
element.getStyle().setProperty("borderBottom", "1px solid white"); //$NON-NLS-1$ //$NON-NLS-2$
}
// Set item's state
item.setState(getItemOldState(item.getUserObject()));
// Recursively update children
for (int n = 0; n < item.getChildCount(); n++) {
TreeItem node = item.getChild(n);
updateItemSelection(node);
}
}
protected Object getEntityId(Object entity) {
return ((BusinessEntity) entity).getId();
}
protected ArrayList<Object> getSelectedEntities() {
ArrayList<Object> selectedEntities = new ArrayList<>();
for (Object entity : (ArrayList<Object>) listModel.getItems()) {
if (selectedItems.contains(getEntityId(entity))) {
selectedEntities.add(entity);
}
}
return selectedEntities;
}
protected void onItemsSelection() {
if (listModel.getItems() == null || (!isRootSelectionEnabled && !isNodeSelectionEnabled)) {
return;
}
ArrayList<Object> selectedEntities = getSelectedEntities();
listModel.setSelectedItem(selectedEntities.isEmpty() ? null : selectedEntities.get(0));
if (isMultiSelection) {
listModel.setSelectedItems(selectedEntities);
}
};
TreeItem findSelectedItem(int clientX, int clientY) {
return findSelectedTreeItemRecursive(null, clientX, clientY);
}
TreeItem findSelectedTreeItemRecursive(TreeItem item, int x, int y) {
if (null == item) {
int count = tree.getItemCount();
for (int i = 0; i < count; i++) {
TreeItem selected = findSelectedTreeItemRecursive(tree.getItem(i), x, y);
if (selected != null) {
return selected;
}
}
return null;
}
int count = item.getChildCount();
for (int i = 0; i < count; i++) {
TreeItem selected = findSelectedTreeItemRecursive(item.getChild(i), x, y);
if (selected != null) {
return selected;
}
}
if (x >= item.getAbsoluteLeft()
&& x <= item.getAbsoluteLeft() + item.getOffsetWidth()
&& y >= item.getAbsoluteTop()
&& y <= item.getAbsoluteTop() + item.getOffsetHeight()) {
return item;
}
return null;
}
}