package org.ovirt.engine.ui.userportal.widget.resources; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.ui.common.SubTableResources; import org.ovirt.engine.ui.common.widget.HasEditorDriver; import org.ovirt.engine.ui.common.widget.editor.EntityModelCellTable; import org.ovirt.engine.ui.common.widget.table.ActionCellTable; import org.ovirt.engine.ui.common.widget.table.cell.CompositeCell; import org.ovirt.engine.ui.common.widget.table.column.AbstractColumn; import org.ovirt.engine.ui.common.widget.table.column.AbstractImageResourceColumn; import org.ovirt.engine.ui.common.widget.table.column.AbstractTextColumn; import org.ovirt.engine.ui.uicommonweb.models.EntityModel; import org.ovirt.engine.ui.uicommonweb.models.ListModel; import org.ovirt.engine.ui.uicommonweb.models.resources.ResourcesModel; import org.ovirt.engine.ui.userportal.ApplicationConstants; import org.ovirt.engine.ui.userportal.ApplicationMessages; import org.ovirt.engine.ui.userportal.ApplicationResources; import org.ovirt.engine.ui.userportal.gin.AssetProvider; import org.ovirt.engine.ui.userportal.uicommon.model.UserPortalDataBoundModelProvider; import com.google.gwt.cell.client.HasCell; import com.google.gwt.core.client.GWT; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.safehtml.client.SafeHtmlTemplates; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Tree; import com.google.gwt.user.client.ui.TreeItem; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.SingleSelectionModel; public class VmTable extends Composite implements HasEditorDriver<ResourcesModel> { private static final VmRowHeaderlessTableResources vmRowResources = GWT.create(VmRowHeaderlessTableResources.class); private static final DiskRowHeaderlessTableResources diskRowResources = GWT.create(DiskRowHeaderlessTableResources.class); private HandlerRegistration openHandler = null; private HandlerRegistration closeHandler = null; private final UserPortalDataBoundModelProvider<VM, ResourcesModel> modelProvider; @UiField(provided = true) ActionCellTable<VM> tableHeader; @UiField Tree vmTree; private static final ApplicationResources resources = AssetProvider.getResources(); private static final ApplicationConstants constants = AssetProvider.getConstants(); private static final ApplicationMessages messages = AssetProvider.getMessages(); private final VmSingleSelectionModel vmSelectionModel = new VmSingleSelectionModel(); interface WidgetUiBinder extends UiBinder<Widget, VmTable> { WidgetUiBinder uiBinder = GWT.create(WidgetUiBinder.class); } public VmTable(UserPortalDataBoundModelProvider<VM, ResourcesModel> modelProvider, SubTableResources headerResources) { this.modelProvider = modelProvider; tableHeader = new ActionCellTable<>(modelProvider, headerResources); initWidget(WidgetUiBinder.uiBinder.createAndBindUi(this)); initTable(); } private void initTable() { tableHeader.addColumn(new EmptyColumn(), constants.virtualMachineSnapshotCreatePopupDescriptionLabel()); tableHeader.addColumn(new EmptyColumn(), constants.disksVm()); tableHeader.addColumn(new EmptyColumn(), constants.virtualSizeVm()); tableHeader.addColumn(new EmptyColumn(), constants.actualSizeVm()); tableHeader.addColumn(new EmptyColumn(), constants.snapshotsVm()); setHeaderColumnWidths(Arrays.asList("40%", "10%", "10%", "10%", "30%")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ tableHeader.setRowData(new ArrayList<VM>()); } @SuppressWarnings("unchecked") @Override public void edit(ResourcesModel model) { if (openHandler != null) { openHandler.removeHandler(); } if (closeHandler != null) { closeHandler.removeHandler(); } clearTreeItems(); vmTree.clear(); for (VM vm : (Iterable<VM>) model.getItems()) { VmTreeItem vmItem = createVmItem(vm); if (vm.getDiskList() != null){ for (DiskImage disk : vm.getDiskList()) { TreeItem diskItem = createDiskItem(disk); vmItem.addItem(diskItem); } } vmTree.addItem(vmItem); } updateSelection(model); listenOnSelectionChange(); } private void clearTreeItems() { int nodeCount = vmTree.getItemCount(); for (int i = 0; i < nodeCount; i++) { clearTreeItems(vmTree.getItem(i)); } } private void clearTreeItems(TreeItem node) { int nodeCount = node.getChildCount(); for (int i = 0; i < nodeCount; i++) { TreeItem child = node.getChild(i); if (child instanceof VmTreeItem) { clearTreeItems(child); } } if (node instanceof VmTreeItem) { ((VmTreeItem) node).reset(); } } @Override public ResourcesModel flush() { return modelProvider.getModel(); } @Override public void cleanup() { // TODO clean up stuff if needed } private void setHeaderColumnWidths(List<String> widths) { for (int i = 0; i < tableHeader.getColumnCount(); i++) { tableHeader.setColumnWidth(tableHeader.getColumn(i), widths.get(i)); } } protected void listenOnSelectionChange() { openHandler = vmTree.addOpenHandler(event -> setSelectionToModel()); closeHandler = vmTree.addCloseHandler(event -> setSelectionToModel()); } private void setSelectionToModel() { List<VM> selectedVMs = new ArrayList<>(); for (int i = 0; i < vmTree.getItemCount(); i++) { if (vmTree.getItem(i) instanceof VmTreeItem) { if (vmTree.getItem(i).getState()) { selectedVMs.add(((VmTreeItem) vmTree.getItem(i)).getVm()); } } } modelProvider.setSelectedItems(selectedVMs); } @SuppressWarnings("unchecked") protected void updateSelection(final ResourcesModel model) { if (model.getSelectedItems() == null || model.getSelectedItems().size() == 0) { return; } for (int i = 0; i < vmTree.getItemCount(); i++) { if (vmTree.getItem(i) instanceof VmTreeItem) { ((VmTreeItem) vmTree.getItem(i)).setState(model.getSelectedItems()); } } } private TreeItem createDiskItem(DiskImage disk) { EntityModelCellTable<ListModel> table = new EntityModelCellTable<>(false, diskRowResources, true); Column<EntityModel, EntityModel> diskWithMappingColumn = new Column<EntityModel, EntityModel>(createDiskImageWithMappingComoisiteCell()) { @Override public EntityModel getValue(EntityModel object) { return object; } }; AbstractTextColumn<EntityModel> paddingColumn = new AbstractTextColumn<EntityModel>() { @Override public String getValue(EntityModel entity) { return asDisk(entity).getDiskAlias(); //$NON-NLS-1$ } }; AbstractTextColumn<EntityModel> virtualSizeColumn = new AbstractTextColumn<EntityModel>() { @Override public String getValue(EntityModel entity) { return messages.gigabytes(String.valueOf(asDisk(entity).getSizeInGigabytes())); } }; AbstractTextColumn<EntityModel> actualSizeColumn = new AbstractTextColumn<EntityModel>() { @Override public String getValue(EntityModel entity) { return messages.gigabytes(String.valueOf( ((Double) asDisk(entity).getActualDiskWithSnapshotsSize()).intValue())); } }; AbstractTextColumn<EntityModel> snapshotsColumn = new AbstractTextColumn<EntityModel>() { @Override public String getValue(EntityModel entity) { return asDisk(entity).getSnapshots().size() + ""; //$NON-NLS-1$ } }; table.addColumn(diskWithMappingColumn, "", "39%"); //$NON-NLS-1$ //$NON-NLS-2$ table.addColumn(paddingColumn, "", "10%"); //$NON-NLS-1$ //$NON-NLS-2$ table.addColumn(virtualSizeColumn, "", "10%"); //$NON-NLS-1$ //$NON-NLS-2$ table.addColumn(actualSizeColumn, "", "10%"); //$NON-NLS-1$ //$NON-NLS-2$ table.addColumn(snapshotsColumn, "", "31%"); //$NON-NLS-1$ //$NON-NLS-2$ EntityModel entityModel = new EntityModel(); entityModel.setEntity(disk); table.setRowData(Arrays.asList(entityModel)); return new TreeItem(table); } private VmTreeItem createVmItem(VM vm) { EntityModelCellTable<ListModel> table = new EntityModelCellTable<>(false, vmRowResources, true); AbstractColumn<EntityModel, EntityModel> vmImageWithNameColumn = new AbstractColumn<EntityModel, EntityModel>(createVmImageWithNameCompositeCell()) { @Override public EntityModel getValue(EntityModel object) { return object; } }; AbstractTextColumn<EntityModel> diskSizeColumn = new AbstractTextColumn<EntityModel>() { @Override public String getValue(EntityModel entity) { ArrayList<DiskImage> diskImages = asVm(entity).getDiskList(); return diskImages != null ? diskImages.size() + "" : "0"; //$NON-NLS-1$ //$NON-NLS-2$ } }; AbstractTextColumn<EntityModel> virtualSizeColumn = new AbstractTextColumn<EntityModel>() { @Override public String getValue(EntityModel entity) { return messages.gigabytes(String.valueOf( ((Double) asVm(entity).getDiskSize()).intValue())); } }; AbstractTextColumn<EntityModel> actualSizeColumn = new AbstractTextColumn<EntityModel>() { @Override public String getValue(EntityModel entity) { return messages.gigabytes(String.valueOf( ((Double) asVm(entity).getActualDiskWithSnapshotsSize()).intValue())); } }; AbstractTextColumn<EntityModel> snapshotsColumn = new AbstractTextColumn<EntityModel>() { @Override public String getValue(EntityModel entity) { ArrayList<DiskImage> diskImages = asVm(entity).getDiskList(); return diskImages != null ? diskImages.size() > 0 ? diskImages.get(0).getSnapshots().size() + "" : "0" : "0"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } }; table.addColumn(vmImageWithNameColumn, "", "39%"); //$NON-NLS-1$ //$NON-NLS-2$ table.addColumn(diskSizeColumn, "", "10%"); //$NON-NLS-1$ //$NON-NLS-2$ table.addColumn(virtualSizeColumn, "", "10%"); //$NON-NLS-1$ //$NON-NLS-2$ table.addColumn(actualSizeColumn, "", "10%"); //$NON-NLS-1$ //$NON-NLS-2$ table.addColumn(snapshotsColumn, "", "31%"); //$NON-NLS-1$ //$NON-NLS-2$ table.setSelectionModel(vmSelectionModel); EntityModel entityModel = new EntityModel(); entityModel.setEntity(vm); table.setRowData(Arrays.asList(entityModel)); return new VmTreeItem(table, vm); } private CompositeCell<EntityModel> createDiskImageWithMappingComoisiteCell() { final AbstractImageResourceColumn<EntityModel> diskImageColumn = new AbstractImageResourceColumn<EntityModel>() { @Override public ImageResource getValue(EntityModel object) { return resources.vmDiskIcon(); } }; final AbstractTextColumn<EntityModel> driveMappingColumn = new AbstractTextColumn<EntityModel>() { @Override public String getValue(EntityModel entity) { return asDisk(entity).getDiskAlias(); //$NON-NLS-1$ } }; return new StyledCompositeCell<>( new ArrayList<HasCell<EntityModel, ?>>(Arrays.asList(diskImageColumn, driveMappingColumn)), cell -> { if (cell == diskImageColumn) { return "float: left"; //$NON-NLS-1$ } else if (cell == driveMappingColumn) { return "float: left; padding-top: 4px; padding-left: 5px;"; //$NON-NLS-1$ } return null; }); } private CompositeCell<EntityModel> createVmImageWithNameCompositeCell() { final AbstractImageResourceColumn<EntityModel> vmImageColumn = new AbstractImageResourceColumn<EntityModel>() { @Override public ImageResource getValue(EntityModel object) { return resources.vmIconWithVmTextInside(); } }; final AbstractTextColumn<EntityModel> nameColumn = new AbstractTextColumn<EntityModel>() { @Override public String getValue(EntityModel entity) { return asVm(entity).getName(); } }; return new StyledCompositeCell<>( new ArrayList<HasCell<EntityModel, ?>>(Arrays.asList(vmImageColumn, nameColumn)), cell -> { if (cell == vmImageColumn) { return "float: left"; //$NON-NLS-1$ } else if (cell == nameColumn) { return "float: left; padding-top: 4px;"; //$NON-NLS-1$ } return null; }); } /** * This class guards, that only one row can be selected at a given time and the selection survives the refresh. The * single instance of this class should be used for more instances of EntityModelCellTable */ static class VmSingleSelectionModel extends SingleSelectionModel<EntityModel> { private VM selectedVM = null; @Override public void setSelected(EntityModel object, boolean selected) { if (object.getEntity() instanceof VM) { if (selected) { selectedVM = (VM) object.getEntity(); super.setSelected(object, true); } else { selectedVM = null; super.setSelected(object, false); } } else { super.setSelected(object, selected); } } @Override public boolean isSelected(EntityModel object) { if (selectedVM == null || !(object.getEntity() instanceof VM)) { return super.isSelected(object); } VM vm = (VM) object.getEntity(); if (vm.getId().equals(selectedVM.getId())) { return true; } else { return false; } } } private VM asVm(EntityModel entity) { return (VM) entity.getEntity(); } private DiskImage asDisk(EntityModel entity) { return (DiskImage) entity.getEntity(); } public interface VmRowHeaderlessTableResources extends CellTable.Resources { interface TableStyle extends CellTable.Style { } @Override @Source({ CellTable.Style.DEFAULT_CSS, "org/ovirt/engine/ui/userportal/css/VmListHeaderlessTable.css" }) TableStyle cellTableStyle(); } public interface DiskRowHeaderlessTableResources extends CellTable.Resources { interface TableStyle extends CellTable.Style { } @Override @Source({ CellTable.Style.DEFAULT_CSS, "org/ovirt/engine/ui/userportal/css/DiskListHeaderlessTable.css" }) TableStyle cellTableStyle(); } /** * An empty column - only for the header */ private static class EmptyColumn extends AbstractTextColumn<VM> { @Override public String getValue(VM object) { return null; } } } class StyledCompositeCell<T> extends CompositeCell<T> { interface CellTemplate extends SafeHtmlTemplates { @Template("<div id=\"{0}\" style=\"{1}\">") SafeHtml div(String id, String style); } private static final CellTemplate templates = GWT.create(CellTemplate.class); private final List<HasCell<T, ?>> hasCells; private final StyledProvider<T> styleProvider; public StyledCompositeCell(List<HasCell<T, ?>> hasCells, StyledProvider<T> styleProvider) { super(hasCells); this.hasCells = hasCells; this.styleProvider = styleProvider; } @Override public void render(Context context, T value, SafeHtmlBuilder sb, String id) { int i = 1; for (HasCell<T, ?> hasCell : hasCells) { String cellId = id + "_" + i; //$NON-NLS-1$ String style = styleProvider.styleStringOf(hasCell) == null ? "" : styleProvider.styleStringOf(hasCell); //$NON-NLS-1$ sb.append(templates.div(cellId, style)); render(context, value, sb, hasCell, id); sb.appendHtmlConstant("</div>"); //$NON-NLS-1$ i++; } } interface StyledProvider<T> { String styleStringOf(HasCell<T, ?> cell); } }