/* * Copyright 2012 Red Hat, Inc. and/or its affiliates. * * 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.drools.workbench.screens.guided.template.client.editor; import java.util.ArrayList; import java.util.List; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Style.Cursor; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.TableCellElement; import com.google.gwt.dom.client.TableElement; import com.google.gwt.dom.client.TableRowElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.EventBus; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.CellPanel; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Image; 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 org.drools.workbench.models.guided.template.shared.TemplateModel; import org.drools.workbench.screens.guided.template.client.editor.events.SetInternalTemplateDataModelEvent; import org.kie.workbench.common.widgets.decoratedgrid.client.widget.AbstractDecoratedGridHeaderWidget; import org.kie.workbench.common.widgets.decoratedgrid.client.widget.DynamicColumn; import org.kie.workbench.common.widgets.decoratedgrid.client.widget.ResourcesProvider; import org.kie.workbench.common.widgets.decoratedgrid.client.widget.SortConfiguration; import org.kie.workbench.common.widgets.decoratedgrid.client.widget.SortDirection; import org.kie.workbench.common.widgets.decoratedgrid.client.widget.events.ColumnResizeEvent; import org.kie.workbench.common.widgets.decoratedgrid.client.widget.events.SetInternalModelEvent; import org.kie.workbench.common.widgets.decoratedgrid.client.widget.events.SortDataEvent; /** * Header for a Vertical Decision Table */ public class TemplateDataHeaderWidget extends AbstractDecoratedGridHeaderWidget<TemplateModel, TemplateDataColumn> { /** * This is the guts of the widget. */ private class HeaderWidget extends CellPanel { /** * A Widget to display sort order */ private class HeaderSorter extends FocusPanel { private final HorizontalPanel hp = new HorizontalPanel(); private final DynamicColumn<TemplateDataColumn> col; private HeaderSorter( final DynamicColumn<TemplateDataColumn> col ) { this.col = col; hp.setHorizontalAlignment( HorizontalPanel.ALIGN_CENTER ); hp.setVerticalAlignment( VerticalPanel.ALIGN_MIDDLE ); hp.setHeight( resources.rowHeaderSorterHeight() + "px" ); hp.setWidth( "100%" ); setIconImage(); add( hp ); // Ensure our icon is updated when the SortDirection changes col.addValueChangeHandler( new ValueChangeHandler<SortConfiguration>() { public void onValueChange( ValueChangeEvent<SortConfiguration> event ) { setIconImage(); } } ); } // Set icon's resource accordingly private void setIconImage() { hp.clear(); switch ( col.getSortDirection() ) { case ASCENDING: switch ( col.getSortIndex() ) { case 0: hp.add( new Image( resources.upArrowIcon() ) ); break; default: hp.add( new Image( resources.smallUpArrowIcon() ) ); } break; case DESCENDING: switch ( col.getSortIndex() ) { case 0: hp.add( new Image( resources.downArrowIcon() ) ); break; default: hp.add( new Image( resources.smallDownArrowIcon() ) ); } break; default: hp.add( new Image( resources.arrowSpacerIcon() ) ); } } } // Child Widgets used in this Widget private List<HeaderSorter> sorters = new ArrayList<HeaderSorter>(); // UI Components private Element[] headerRows = new Element[ 2 ]; private List<DynamicColumn<TemplateDataColumn>> headerColumns = new ArrayList<DynamicColumn<TemplateDataColumn>>(); // Constructor private HeaderWidget() { for ( int iRow = 0; iRow < headerRows.length; iRow++ ) { headerRows[ iRow ] = DOM.createTR(); getBody().appendChild( headerRows[ iRow ] ); } getBody().getParentElement().<TableElement>cast().setCellSpacing( 0 ); getBody().getParentElement().<TableElement>cast().setCellPadding( 0 ); } // Make default header label private Element makeLabel( String text, int width, int height ) { Element div = DOM.createDiv(); div.getStyle().setWidth( width, Unit.PX ); div.getStyle().setHeight( height, Unit.PX ); div.getStyle().setOverflow( Overflow.HIDDEN ); div.setInnerText( text ); return div; } // Populate a default header element private void populateTableCellElement( DynamicColumn<TemplateDataColumn> col, Element tce ) { TemplateDataColumn modelCol = col.getModelColumn(); tce.appendChild( makeLabel( modelCol.getTemplateVar(), col.getWidth(), resources.rowHeaderHeight() ) ); tce.addClassName( resources.headerRowIntermediate() ); } // Redraw entire header private void redraw() { // Remove existing widgets from the DOM hierarchy for ( HeaderSorter sorter : sorters ) { remove( sorter ); } sorters.clear(); // Extracting visible columns makes life easier headerColumns.clear(); for ( int iCol = 0; iCol < sortableColumns.size(); iCol++ ) { DynamicColumn<TemplateDataColumn> col = sortableColumns.get( iCol ); headerColumns.add( col ); } // Draw rows for ( int iRow = 0; iRow < headerRows.length; iRow++ ) { redrawHeaderRow( iRow ); } fireResizeEvent(); } // Schedule resize event after header has been drawn or resized private void fireResizeEvent() { Scheduler.get().scheduleDeferred( new ScheduledCommand() { public void execute() { ResizeEvent.fire( TemplateDataHeaderWidget.this, widget.getOffsetWidth(), widget.getOffsetHeight() ); } } ); } // Redraw a single row obviously private void redrawHeaderRow( int iRow ) { Element tce = null; Element tre = DOM.createTR(); switch ( iRow ) { case 0: for ( DynamicColumn<TemplateDataColumn> col : headerColumns ) { tce = DOM.createTD(); tce.addClassName( resources.headerText() ); tre.appendChild( tce ); populateTableCellElement( col, tce ); } break; case 1: // Sorters for ( DynamicColumn<TemplateDataColumn> col : headerColumns ) { final HeaderSorter shp = new HeaderSorter( col ); final DynamicColumn<TemplateDataColumn> sortableColumn = col; if ( !isReadOnly ) { shp.addClickHandler( new ClickHandler() { public void onClick( ClickEvent event ) { if ( sortableColumn.isSortable() ) { updateSortOrder( sortableColumn ); SortDataEvent sde = new SortDataEvent( getSortConfiguration() ); eventBus.fireEvent( sde ); } } } ); } sorters.add( shp ); tce = DOM.createTD(); tce.addClassName( resources.headerRowBottom() ); tre.appendChild( tce ); add( shp, tce ); } break; } getBody().replaceChild( tre, headerRows[ iRow ] ); headerRows[ iRow ] = tre; } // Update sort order. The column clicked becomes the primary sort column // and the other, previously sorted, columns degrade in priority private void updateSortOrder( DynamicColumn<TemplateDataColumn> column ) { if ( column.getSortIndex() == 0 ) { if ( column.getSortDirection() != SortDirection.ASCENDING ) { column.setSortDirection( SortDirection.ASCENDING ); } else { column.setSortDirection( SortDirection.DESCENDING ); } } else { column.setSortIndex( 0 ); column.setSortDirection( SortDirection.ASCENDING ); int sortIndex = 1; for ( DynamicColumn<TemplateDataColumn> sortableColumn : sortableColumns ) { if ( !sortableColumn.equals( column ) ) { if ( sortableColumn.getSortDirection() != SortDirection.NONE ) { sortableColumn.setSortIndex( sortIndex ); sortIndex++; } } } } } } // UI Components private HeaderWidget widget; /** * Construct a "Header" for the provided DecoratedGridWidget * @param resources * @param eventBus * @param isReadOnly */ public TemplateDataHeaderWidget( final ResourcesProvider<TemplateDataColumn> resources, final boolean isReadOnly, final EventBus eventBus ) { super( resources, isReadOnly, eventBus ); //Wire-up event handlers eventBus.addHandler( SetInternalTemplateDataModelEvent.TYPE, this ); addResizeHandler( new ResizeHandler() { @Override public void onResize( final ResizeEvent event ) { final int width = event.getWidth(); panel.setWidth( width + "px" ); } } ); } @Override public void redraw() { widget.redraw(); } @Override public void setScrollPosition( int position ) { if ( position < 0 ) { throw new IllegalArgumentException( "position cannot be null" ); } ( (ScrollPanel) this.panel ).setHorizontalScrollPosition( position ); } // Resize the inner DIV in each table cell protected void resizeColumn( DynamicColumn<TemplateDataColumn> resizeColumn, int resizeColumnWidth ) { DivElement div; TableCellElement tce; // This is also set in the ColumnResizeEvent handler, however it makes // resizing columns in the header more simple too resizeColumn.setWidth( resizeColumnWidth ); int resizeColumnIndex = widget.headerColumns.indexOf( resizeColumn ); // Row 0 (General\Fact Type) tce = widget.headerRows[ 0 ].getChild( resizeColumnIndex ).<TableCellElement>cast(); div = tce.getFirstChild().<DivElement>cast(); div.getStyle().setWidth( resizeColumnWidth, Unit.PX ); // Row 1 (Sorters) tce = widget.headerRows[ 1 ].getChild( resizeColumnIndex ).<TableCellElement>cast(); div = tce.getFirstChild().<DivElement>cast(); div.getStyle().setWidth( resizeColumnWidth, Unit.PX ); // Fire event to any interested consumers ColumnResizeEvent cre = new ColumnResizeEvent( widget.headerColumns.get( resizeColumnIndex ), resizeColumnWidth ); eventBus.fireEvent( cre ); } @Override protected Widget getHeaderWidget() { if ( this.widget == null ) { this.widget = new HeaderWidget(); } return this.widget; } @Override protected ResizerInformation getResizerInformation( int mx ) { boolean isPrimed = false; ResizerInformation resizerInfo = new ResizerInformation(); for ( int iCol = 0; iCol < widget.headerRows[ 0 ].getChildCount(); iCol++ ) { TableCellElement tce = widget.headerRows[ 0 ].getChild( iCol ).<TableCellElement>cast(); int cx = tce.getAbsoluteRight(); if ( Math.abs( mx - cx ) <= 5 ) { isPrimed = true; resizerInfo.setResizePrimed( isPrimed ); resizerInfo.setResizeColumn( widget.headerColumns.get( iCol ) ); resizerInfo.setResizeColumnLeft( tce.getAbsoluteLeft() ); break; } } if ( isPrimed ) { setCursorType( Cursor.COL_RESIZE ); } else { setCursorType( Cursor.DEFAULT ); } return resizerInfo; } // Set the cursor type for all cells on the table as // we only use rowHeader[0] to check which column // needs resizing however the mouse could be over any // row private void setCursorType( Cursor cursor ) { for ( int iRow = 0; iRow < widget.headerRows.length; iRow++ ) { TableRowElement tre = widget.headerRows[ iRow ].<TableRowElement>cast(); for ( int iCol = 0; iCol < tre.getCells().getLength(); iCol++ ) { TableCellElement tce = tre.getCells().getItem( iCol ); tce.getStyle().setCursor( cursor ); } } } public void onSetInternalModel( SetInternalModelEvent<TemplateModel, TemplateDataColumn> event ) { this.sortableColumns.clear(); this.model = event.getModel(); List<DynamicColumn<TemplateDataColumn>> columns = event.getColumns(); for ( DynamicColumn<TemplateDataColumn> column : columns ) { sortableColumns.add( column ); } redraw(); } }