/*
* 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.kie.workbench.common.widgets.decoratedgrid.client.widget;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.TableElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.ContextMenuEvent;
import com.google.gwt.event.dom.client.ContextMenuHandler;
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.Event;
import com.google.gwt.user.client.EventListener;
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.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import org.kie.workbench.common.widgets.decoratedgrid.client.widget.events.DeleteRowEvent;
import org.kie.workbench.common.widgets.decoratedgrid.client.widget.events.InsertRowEvent;
import org.kie.workbench.common.widgets.decoratedgrid.client.widget.events.PasteRowsEvent;
import org.kie.workbench.common.widgets.decoratedgrid.client.widget.events.ToggleMergingEvent;
import org.kie.workbench.common.widgets.decoratedgrid.client.widget.data.DynamicData;
import org.kie.workbench.common.widgets.decoratedgrid.client.widget.data.RowMapper;
import org.kie.workbench.common.widgets.decoratedgrid.client.widget.events.RowGroupingChangeEvent;
import org.kie.workbench.common.widgets.decoratedgrid.client.widget.events.AppendRowEvent;
import java.util.ArrayList;
/**
* A sidebar for a VericalDecisionTable. This provides a vertical list of
* controls to add and remove the associated row from the DecisionTable.
*/
public abstract class AbstractVerticalDecoratedGridSidebarWidget<M, T> extends AbstractDecoratedGridSidebarWidget<M, T> {
/**
* Widget to render selectors beside rows. Two selectors are provided per
* row: (1) A "add new row (above selected)" and (2) "delete row".
*/
private class VerticalSelectorWidget extends CellPanel {
// Widgets (selectors) created (so they can be removed later)
private ArrayList<Widget> widgets = new ArrayList<Widget>();
private VerticalSelectorWidget( EventBus eventBus ) {
getBody().getParentElement().<TableElement>cast().setCellSpacing( 0 );
getBody().getParentElement().<TableElement>cast().setCellPadding( 0 );
sinkEvents( Event.getTypeInt( "click" ) );
}
// Append a row to the end
public void appendRow() {
//UI Components
Element tre = DOM.createTR();
Element tce = DOM.createTD();
tre.setClassName( getRowStyle( widgets.size() ) );
tce.getStyle().setHeight( resources.rowHeight(),
Unit.PX );
tce.addClassName( resources.selectorCell() );
DOM.appendChild( getBody(),
tre );
tre.appendChild( tce );
Widget widget = makeRowWidget();
add( widget,
tce );
widgets.add( widget );
fixStyles( widgets.size() );
}
// Insert a new row before the given index
public void insertRowBefore( int index ) {
//UI Components
Element tre = DOM.createTR();
Element tce = DOM.createTD();
tre.setClassName( getRowStyle( widgets.size() ) );
tce.getStyle().setHeight( resources.rowHeight(),
Unit.PX );
tce.addClassName( resources.selectorCell() );
DOM.insertChild( getBody(),
tre,
index );
tre.appendChild( tce );
Widget widget = makeRowWidget();
add( widget,
tce );
widgets.add( index,
widget );
fixStyles( index );
}
// Delete a row at the given index
public void deleteRow( int index ) {
//UI Components
Widget widget = widgets.get( index );
remove( widget );
getBody().<TableSectionElement>cast().deleteRow( index );
widgets.remove( index );
fixStyles( index );
}
// Redraw sidebar with the given number of rows
private void redraw() {
//Remove existing
final int rowsToRemove = widgets.size();
for ( int iRow = 0; iRow < rowsToRemove; iRow++ ) {
deleteRow( 0 );
}
//Add selector for each row
for ( int iRow = 0; iRow < data.size(); iRow++ ) {
appendRow();
}
}
// Row styles need to be re-applied after inserting and deleting rows
private void fixStyles( int iRow ) {
while ( iRow < getBody().getChildCount() ) {
TableRowElement tre = getBody().getChild( iRow ).<TableRowElement>cast();
tre.setClassName( getRowStyle( iRow ) );
iRow++;
}
}
// Get style applicable to row
private String getRowStyle( int iRow ) {
boolean isEven = iRow % 2 == 0;
String trClasses = isEven ? resources.cellTableEvenRow() : resources.cellTableOddRow();
return trClasses;
}
// Make the selector Widget
private Widget makeRowWidget() {
final HorizontalPanel hp = new HorizontalPanel();
hp.setVerticalAlignment( VerticalPanel.ALIGN_MIDDLE );
hp.setHorizontalAlignment( HorizontalPanel.ALIGN_CENTER );
hp.setWidth( resources.sidebarWidth() + "px" );
//Add row icon
if ( !isReadOnly ) {
FocusPanel fp = new FocusPanel();
fp.setHeight( "100%" );
fp.setWidth( "50%" );
fp.add( new Image( resources.selectorAddIcon() ) );
if ( !isReadOnly ) {
fp.addClickHandler( new ClickHandler() {
public void onClick( ClickEvent event ) {
//Raise an event to add row
int index = rowMapper.mapToAbsoluteRow( widgets.indexOf( hp ) );
InsertRowEvent ire = new InsertRowEvent( index );
eventBus.fireEvent( ire );
}
} );
}
hp.add( fp );
} else {
SimplePanel sp = new SimplePanel();
sp.setHeight( "100%" );
sp.setWidth( "50%" );
sp.add( new Image( resources.selectorAddIcon() ) );
hp.add( sp );
}
//Delete row icon
if ( !isReadOnly ) {
FocusPanel fp = new FocusPanel();
fp.setHeight( "100%" );
fp.setWidth( "50%" );
fp.add( new Image( resources.selectorDeleteIcon() ) );
fp.addClickHandler( new ClickHandler() {
public void onClick( ClickEvent event ) {
//Raise an event to delete row
int index = rowMapper.mapToAbsoluteRow( widgets.indexOf( hp ) );
DeleteRowEvent ire = new DeleteRowEvent( index );
eventBus.fireEvent( ire );
}
} );
hp.add( fp );
} else {
SimplePanel sp = new SimplePanel();
sp.setHeight( "100%" );
sp.setWidth( "50%" );
sp.add( new Image( resources.selectorDeleteIcon() ) );
hp.add( sp );
}
//Add a context menu to copy\paste rows
if ( !isReadOnly ) {
hp.addDomHandler( new ContextMenuHandler() {
public void onContextMenu( ContextMenuEvent event ) {
//Prevent the default context menu
event.preventDefault();
event.stopPropagation();
//Set source row index and show
int clientX = event.getNativeEvent().getClientX();
int clientY = event.getNativeEvent().getClientY();
showContextMenu( widgets.indexOf( hp ),
clientX,
clientY );
}
},
ContextMenuEvent.getType() );
}
return hp;
}
}
/**
* Simple spacer to ensure scrollable part of sidebar aligns with grid.
*/
private class VerticalSideBarSpacerWidget extends CellPanel
implements
ToggleMergingEvent.Handler {
private Image icon = new Image();
private Element tre = DOM.createTR();
private Element tce = DOM.createTD();
private Element outerDiv = DOM.createDiv();
private Element innerDiv = DOM.createDiv();
private boolean isMerged = false;
public void setHeight( int height ) {
super.setHeight( height + "px" );
}
private void setPadding( int padding ) {
getBody().getParentElement().<TableElement>cast().setCellPadding( 0 );
}
private VerticalSideBarSpacerWidget() {
// Create DOM structure. The spacer is constructed of a single cell HTML table
// containing two nested DIVs. These DIVs are used to control the row height
// across all browsers and centre the toggle merging icon.
setSpacing( 0 );
setPadding( 0 );
setIconImage( isMerged );
tce.addClassName( resources.selectorSpacer() );
innerDiv.addClassName( resources.selectorSpacerInnerDiv() );
outerDiv.addClassName( resources.selectorSpacerOuterDiv() );
tre.appendChild( tce );
tce.appendChild( outerDiv );
outerDiv.appendChild( innerDiv );
innerDiv.appendChild( icon.getElement() );
getBody().appendChild( tre );
//This could be moved to CSS if we always knew the icon size
innerDiv.getStyle().setHeight( icon.getHeight(),
Unit.PX );
innerDiv.getStyle().setMarginTop( ( icon.getHeight() / 2 ) * -1,
Unit.PX );
innerDiv.getStyle().setWidth( icon.getWidth(),
Unit.PX );
innerDiv.getStyle().setMarginLeft( ( icon.getWidth() / 2 ) * -1,
Unit.PX );
// Setup event handling
DOM.setEventListener( icon.getElement(),
new EventListener() {
public void onBrowserEvent( Event event ) {
if ( event.getType().equals( "click" ) ) {
//Raise event to toggle merging
ToggleMergingEvent tme = new ToggleMergingEvent( !isMerged );
eventBus.fireEvent( tme );
}
}
} );
DOM.sinkEvents( icon.getElement(),
Event.getTypeInt( "click" ) );
eventBus.addHandler( ToggleMergingEvent.TYPE,
this );
}
public void onToggleMerging( ToggleMergingEvent event ) {
isMerged = event.isMerged();
setIconImage( isMerged );
}
// Set the icon's image accordingly
private void setIconImage( boolean isMerged ) {
if ( isMerged ) {
icon.setResource( resources.toggleUnmergeIcon() );
} else {
icon.setResource( resources.toggleMergeIcon() );
}
}
}
// UI Elements
private ScrollPanel scrollPanel;
private VerticalSelectorWidget selectors;
private VerticalSideBarSpacerWidget spacer = new VerticalSideBarSpacerWidget();
//Underlying model
protected DynamicData data;
protected RowMapper rowMapper;
/**
* Construct a "Sidebar" for the provided DecisionTable
* @param resources
* @param isReadOnly
* @param eventBus
*/
public AbstractVerticalDecoratedGridSidebarWidget( ResourcesProvider<T> resources,
boolean isReadOnly,
EventBus eventBus ) {
// Argument validation performed in the superclass constructor
super( resources,
isReadOnly,
eventBus );
// Construct the Widget
scrollPanel = new ScrollPanel();
VerticalPanel container = new VerticalPanel();
selectors = new VerticalSelectorWidget( eventBus );
container.add( spacer );
container.add( scrollPanel );
scrollPanel.add( selectors );
// We don't want scroll bars on the Sidebar
scrollPanel.getElement().getStyle().setOverflow( Overflow.HIDDEN );
initWidget( container );
}
@Override
void resizeSidebar( int height ) {
if ( height < 0 ) {
throw new IllegalArgumentException( "height cannot be less than zero" );
}
spacer.setHeight( height );
}
@Override
public void setHeight( String height ) {
if ( height == null ) {
throw new IllegalArgumentException( "height cannot be null" );
}
this.scrollPanel.setHeight( height );
}
@Override
public void setScrollPosition( int position ) {
if ( position < 0 ) {
throw new IllegalArgumentException( "position cannot be less than zero" );
}
this.scrollPanel.setVerticalScrollPosition( position );
}
@Override
protected void redraw() {
selectors.redraw();
}
public void onDeleteRow( DeleteRowEvent event ) {
int index = rowMapper.mapToMergedRow( event.getIndex() );
selectors.deleteRow( index );
}
public void onInsertRow( InsertRowEvent event ) {
int index = rowMapper.mapToMergedRow( event.getIndex() );
selectors.insertRowBefore( index );
}
public void onAppendRow( AppendRowEvent event ) {
selectors.appendRow();
}
public void onPasteRows( PasteRowsEvent event ) {
int iRow = rowMapper.mapToMergedRow( event.getTargetRowIndex() );
selectors.insertRowBefore( iRow );
}
public void onRowGroupingChange( RowGroupingChangeEvent event ) {
selectors.redraw();
}
}