at.spardat.xma.mdl.table.TableUIDelegateClient Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* s IT Solutions AT Spardat GmbH - initial API and implementation
*******************************************************************************/
// @(#) $Id: TableUIDelegateClient.java 9890 2012-09-04 13:30:14Z hoenninger $
package at.spardat.xma.mdl.table;
import java.text.Collator;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.ResourceBundle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Widget;
import at.spardat.enterprise.fmt.AParseException;
import at.spardat.enterprise.fmt.ASelectionFmt;
import at.spardat.enterprise.fmt.IFmt;
import at.spardat.enterprise.util.Types;
import at.spardat.xma.component.ComponentClient;
import at.spardat.xma.mdl.Atom;
import at.spardat.xma.mdl.AttachmentExceptionClient;
import at.spardat.xma.mdl.ModelChangeEvent;
import at.spardat.xma.mdl.UIDelegateClient;
import at.spardat.xma.mdl.ValidationErrorClient;
import at.spardat.xma.mdl.WModel;
import at.spardat.xma.mdl.table.XMATableColumn.AtomComparator;
import at.spardat.xma.mdl.util.TransAtomTable;
import at.spardat.xma.page.EventAdapter;
import at.spardat.xma.page.PageClient;
import at.spardat.xma.page.Scaler;
import at.spardat.xma.util.Assert;
/**
* This class manages the UI related aspects of a TableWMClient widget model
*
* @author YSD, 27.04.2003 16:29:53
*/
public class TableUIDelegateClient extends UIDelegateClient {
/** resource bundle containing all localized strings of PagingControlClient */
private static final String resourceBundle = "at.spardat.xma.mdl.table.Table";
//Pathes to the sort-arrow-icons - used if setSortIndicator(true)
private static final String ARROW_UP_ICON = "at/spardat/xma/mdl/table/icons/sortarrow_up.gif";
private static final String ARROW_DOWN_ICON = "at/spardat/xma/mdl/table/icons/sortarrow_down.gif";
private static final String ARROW_EMPTY_ICON = "at/spardat/xma/mdl/table/icons/sortarrow_empty.gif";
/**
* The widget model this UI delegate belongs to
*/
protected TableWMClient wModel_;
/**
* drawn from the table model and redundantly stored here
*/
private int columnCount_;
/**
* The SWT table
*/
private Table table_;
/**
* The opional label usually positioned at the left hand side of the widget.
*/
private Label label_;
/**
* Keep track of whether we are updating the UI.
*/
private boolean updatingUI_ = false;
/**
* The model index of the column used to sort the table. Is -1 if
* the table is shown in natural order.
*/
private int sortingCol_ = -1;
/**
* If sortingCol_ != -1, indicates if the column is sorted ascending
*/
private boolean ascending = true;
/**
* Holds as many Integer-objects as there are rows in the widget
* model. The entry e(i) at index i specifies that the row
* at index i in the SWT table maps to the row at
* index e(i) in the widget model.
*/
private ArrayList sortPermutation_;
/**
* Maps SWT column indexes to model indexes. The SWT column
* at index i belongs to the model column at
* index swtCol2ModelCol_[i].
*/
private int [] swtCol2ModelCol_;
/**
* Maps model column indexes to SWT column indexes. The
* model column at index i is mapped to the SWT
* table column at index modelCol2SwtCol_[i].
*/
private int [] modelCol2SwtCol_;
/**
* Editable-state
*/
private boolean editable_ = true;
/**
* Enable-state
*/
protected boolean enabled_ = true;
/**
* If showSortIndicator_ is true then a sort indicator is shown at the sorting column's header.
*/
private boolean showSortIndicator_ = false;
/**
* external sorter to use instead of the internal implementation if not null
*/
private TableExternalSorterClient externalSorter_;
/**
* row comparator used for sorting the rows
*/
private RowComparator rowComparator_;
/**
* Menu Item that allows for copying value to clipboard
*/
private MenuItem copyMenuItem_;
/**
* Menu Item that allows for copying value to clipboard
*/
private String copyMenuClipboardText_;
/**
* Constructor
*
* @param wModel the widget model this UI delegate belongs to
*/
public TableUIDelegateClient (TableWMClient wModel) {
wModel_ = wModel;
columnCount_ = wModel.columnCount_;
}
/**
* The attached Table must delegate its SelectionEvent to this method in order
* to keep track of the selection state of the table. Moreover, if the table
* is sortable, the SelectionEvents of every TableColumn must also be
* notified.
*
* @see at.spardat.xma.mdl.UIDelegateClient#handleUIEvent(java.lang.Object, int)
*/
public void handleUIEvent (Object event, int type) {
if (!isUIAttached()) return;
if (updatingUI_) {
return;
}
if (Assert.ON) debugInvariant();
realInvariant();
updatingUI_ = true;
try {
handleUIEventInternal(event);
} finally {
updatingUI_ = false;
}
if (Assert.ON) debugInvariant();
realInvariant();
}
protected void handleUIEventInternal (Object event) {
if (event instanceof SelectionEvent) {
SelectionEvent selectionEvent = (SelectionEvent) event;
// selection event on the table
if (selectionEvent.widget == table_) {
handleTableSelect();
} else if (selectionEvent.widget instanceof TableColumn) {
handleColumnSelect(selectionEvent);
} else if (selectionEvent.widget == copyMenuItem_) {
handleCopy();
}
} else if (event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent)event;
if (mouseEvent.widget == table_ && mouseEvent.button == 3){
// Rightclick in table: Find out value for copy of text in table cell
TableItem[] selection = table_.getSelection();
int column = getColumnAt(table_, mouseEvent);
copyMenuClipboardText_ = selection.length>0 ? selection[0].getText(column) : "";
ResourceBundle messages = ResourceBundle.getBundle(resourceBundle,getLocale());
String menuText = MessageFormat.format(messages.getString("copyCellText"), new Object[] { copyMenuClipboardText_} );
if (copyMenuItem_!= null)
copyMenuItem_.setText(menuText);
}
}
}
protected void handleCopy() {
// Context menu selected: copy cell value to clipboard
if (copyMenuClipboardText_ != null && copyMenuClipboardText_.length()>0) {
Display display = table_.getShell().getDisplay();
Clipboard clipboard = new Clipboard(display);
clipboard.setContents(new String[] {copyMenuClipboardText_}, new Transfer[] {TextTransfer.getInstance()});
clipboard.dispose();
}
}
protected void handleColumnSelect(SelectionEvent selectionEvent) {
// selection event on one of the tables columns causes table to be sorted
Widget w = selectionEvent.widget;
TableColumn[] cols = table_.getColumns();
for (int i=0; i 0) deselectAllOnWidget();
} else {
int [] swtIndexes = table_.getSelectionIndices();
for (int i=0; imodelIndexLowerBound by an increment of amount.
*/
private void adjustModelIndexes (int modelIndexLowerBound, int amount) {
if (sortingCol_ == -1) {
for (int i=modelIndexLowerBound, size=sortPermutation_.size(); i= modelIndexLowerBound) setPermIndex(i, val+amount);
}
}
}
/**
* Returns the integer value in the permutation array at index i
*/
private int getPermIndex (int i) {
return ((Integer)sortPermutation_.get(i)).intValue();
}
/**
* Sets the value in the permutation array at index i to value
*/
private void setPermIndex (int i, int value) {
sortPermutation_.set(i, new Integer(value));
}
/**
* Adds a new SWT table item and keeps the sortPermutations
* array up to date.
*
* @param swtIndex the row index of the SWT table
* @param modelIndex the row index of the widget model
* @return newly created TableItem
*/
private TableItem newSWTRow (int swtIndex, int modelIndex) {
sortPermutation_.add(swtIndex, new Integer(modelIndex));
TableItem item = new TableItem (table_, SWT.NULL, swtIndex);
callItemCreated(item);
return item;
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#getWModel()
*/
public WModel getWModel() {
return wModel_;
}
// see at.spardat.xma.mdl.UIDelegateClient.createControl()
public Object createControl(Object parent) {
if(!(parent instanceof Composite)) throw new IllegalArgumentException("parent must be a composite");
Composite parentComp = (Composite)parent;
Table table;
if(wModel_.isMultiSelect()) {
table = new Table(parentComp, SWT.MULTI|SWT.BORDER|SWT.FULL_SELECTION);
} else {
table = new Table(parentComp, SWT.SINGLE|SWT.BORDER|SWT.FULL_SELECTION);
}
table.setHeaderVisible(true);
table.setLinesVisible(true);
int cols = wModel_.getColumnCount();
for(int i=0;i deltaX)
return columnIndex;
columnIndex++;
}
return columnIndex - 1;
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#isUIAttached()
*/
public boolean isUIAttached() {
return table_ != null;
}
/**
* Requirements on the attached SWT table are:
*
* - The number of visible columns in the model must match the number of columns of the SWT table.
*
- If style SWT.CHECK or SWT.MULTI is set, the table model must be
* constructed with style S_MULTI_SELECT.
*
*
* @see at.spardat.xma.mdl.UIDelegateClient#attachUI(java.lang.Object, java.lang.Object)
*/
public void attachUI (Object control, Object label) throws AttachmentExceptionClient {
if (isUIAttached()) throw new IllegalStateException();
Table ta = null;
ta = attachUIInternal(control);
// save label
if (label != null && label instanceof Label) {
label_ = (Label) label;
}
int style = ta.getStyle();
int numSWTCols = ta.getColumnCount();
boolean tableIsMulti = (style & SWT.CHECK) != 0 || (style & SWT.MULTI) != 0;
if (tableIsMulti != wModel_.isMultiSelect())
throw new AttachmentExceptionClient ("SWT table and model selection cardinality does not match.");
/**
* set up the mapping between model and swt columns
*/
swtCol2ModelCol_ = new int[columnCount_]; // could be shorter, but who cares.
modelCol2SwtCol_ = new int[columnCount_];
int j = 0;
for (int i=0; i0)
copyMenuItem_ = ta.getMenu().getItem(0);
}
else {
throw new AttachmentExceptionClient ("TableDirect must be attached to a SWT-table or Nebula TableCombo");
}
return ta;
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#getUIControl()
*/
public Object getUIControl() {
if (!isUIAttached()) throw new IllegalStateException ();
return table_;
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#getUILabel()
*/
public Object getUILabel() {
if (!isUIAttached()) throw new IllegalStateException ();
return label_;
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#detachUI()
*/
public void detachUI() {
if (!isUIAttached()) return;
updatingUI_ = true;
try {
enabled_=table_.getEnabled();
table_ = null;
label_ = null;
} finally {
updatingUI_ = false;
}
}
/**
* This method first removes all rows from the SWT table. It
* created as many SWT rows as there are rows in the widget model
* using the right sort order.
*/
private void rows2UI () {
table_.removeAll();
createUnsortedPermutation();
// sort
sort ();
// create the SWT rows
createSWTRows();
updateErrorState();
}
/**
* Grabs the selection information from the model and updates the UI.
*/
private void selection2UI () {
if (checkedSelection()) {
// iterate over the SWT table and correct every check box
for (int i=0, size=table_.getItemCount(); i 0){
Image image = null;
try {
short valueShort = Short.parseShort(a.toString());
if(valueShort == 0) text = "";
image = ((PageClient)wModel_.getPage()).getComponent().getImage(valueShort);
} catch (NumberFormatException e) {
}
if (image != null) ti.setImage(i, image);
else ti.setText(i, text);
}else{
ti.setText(i, text);
}
if(wModel_.wm2UIlistener_ != null){ //call an registered listener
wModel_.wm2UIlistener_.model2UIEvent(tr, ti, modelColumn, i);
}
}
short imageId = (short) tr.getImageId();
if (imageId > 0) {
// set image
Image image = ((PageClient)wModel_.getPage()).getComponent().getImage(imageId);
if (image != null) ti.setImage(image);
}
}
/**
* Creates a sort permutation in instance variable sortPermutation
* that maps the SWT and model indices one to one.
*/
private void createUnsortedPermutation () {
sortPermutation_ = new ArrayList (wModel_.size());
for (int i=0, size=wModel_.size(); isort-calls.
*/
protected void sortNatural () {
if (!isUIAttached()) return;
int topIndex = table_.getTopIndex();
if (sortingCol_ != -1) {
sortingCol_ = -1;
ascending = true;
rows2UI();
selection2UI();
table_.setTopIndex(topIndex); // restore top index
//erease the old sort indicator
actualizeSortIndicator2UI();
}
}
/**
* Sorts the table. Physically, the sort permutation in instance
* variable sortPermutation is changed.
*/
protected void sort () {
if (sortingCol_ != -1 && allCellsInColumnOfSameType(sortingCol_)) {
Collator collator = Collator.getInstance (getLocale());
collator.setStrength (Collator.SECONDARY); // todo parametrize this
prepareRowComparator (wModel_.table_, sortingCol_, ascending, collator,wModel_.getColumn(sortingCol_).getComparator());
Collections.sort (sortPermutation_, rowComparator_);
}
}
/**
* Creates a new RowComparator which sorts the rows of a table.
*
* @return
*/
protected RowComparator prepareRowComparator(TransAtomTable table, int sortingCol, boolean ascending, Collator collator, AtomComparator comparator) {
if (rowComparator_ == null) {
rowComparator_ = new RowComparator();
}
rowComparator_.prepare(table,sortingCol,ascending,collator,comparator);
return rowComparator_;
}
/**
* Gets the locale from the attached widget
*/
protected Locale getLocale () {
return ((PageClient)wModel_.getPage()).getComponent().getSession().getContext().getLocale();
}
/**
* Determines if all Atoms in column colIndex are of the same
* type or null. Only such columns are sortable.
*
* @param colIndex zero based index of a column in the model.
*/
protected boolean allCellsInColumnOfSameType (int colIndex) {
byte type = Types.FIRST-1;
for (int i=0, size=wModel_.table_.size(); iTableItem that belongs to the row at
* index modelIndex in the widget model.
*/
TableItem getTableItemForModelRowIndex (int modelIndex) {
return table_.getItem(modelIndex2SwtIndex(modelIndex));
}
/**
* Invariant not indended to be used in production
*/
private void debugInvariant () {
if (isUIAttached()) {
// check the permutations array
if (sortPermutation_.size() != wModel_.size()) throw new RuntimeException ();
for (int i=0, size=sortPermutation_.size(); i wModel_.size()) throw new RuntimeException ("wrong model index " + modelIndex);
}
}
}
/**
* Invariant which should also be checked in production code
*/
private void realInvariant () {
// number of rows in the table must be equal to the number of rows in the model
if (isUIAttached()) {
if (table_.getItemCount() != wModel_.size()) {
throw new RuntimeException ("table size mismatch, SWT: " + table_.getItemCount() + ", model: " + wModel_.size());
}
}
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#isEnabled()
*/
public boolean isEnabled () {
if (isUIAttached()) {
//always take the state from the widget (if it was set directly on it) as this is the old behavior
enabled_ = table_.isEnabled();
}
return enabled_;
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#setEnabled(boolean)
*/
public void setEnabled (boolean what) {
enabled_ = what;
if (isUIAttached()) {
table_.setEnabled(what);
}
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#isEditable()
*/
public boolean isEditable () {
return editable_;
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#setEditable(boolean)
*/
public void setEditable (boolean what) {
editable_ = what;
}
/**
* Not implemented - does nothing
*/
public void setErrorColor (boolean inError) {
}
/**
* To compare two entries of the sortPermutation instance variable
*
* @author YSD, 27.04.2003 17:22:49
*/
static public class RowComparator implements Comparator {
/**
* Constructor
*
*/
public RowComparator() {
}
/**
*
* @param table the TransAtomTable of the widget model that holds the rows
* @param sortingCol index of the column which determines sort order
* @param ascending true if sorting is in ascending order
* @param collator used to sort strings
*/
public void prepare(TransAtomTable table, int sortingCol, boolean ascending, Collator collator, AtomComparator comparator) {
table_ = table;
sortingCol_ = sortingCol;
ascending_ = ascending;
collator_ = collator;
comparator_ = comparator;
}
/**
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare (Object o1, Object o2) {
Integer i1 = (Integer)o1;
Integer i2 = (Integer)o2;
Atom a1 = table_.get(i1.intValue(), sortingCol_);
Atom a2 = table_.get(i2.intValue(), sortingCol_);
int cmpVal;
// first, treat special case if Atoms are null
if (a1 == null && a2 == null) cmpVal = 0;
else if (a1 == null) cmpVal = -1;
else if (a2 == null) cmpVal = 1;
else if (comparator_ != null){
cmpVal = comparator_.compare(a1, a2);
}
else if (a1.getType() == Types.T_STRING) {
cmpVal = collator_.compare(a1.toString(), a2.toString());
} else cmpVal = a1.compareTo(a2);
return ascending_ ? cmpVal : cmpVal * -1;
}
protected TransAtomTable table_;
protected int sortingCol_;
protected boolean ascending_;
/**
* for string sorting
*/
protected Collator collator_;
/**
* The XMATableColumns custom comparator
*/
protected AtomComparator comparator_;
}
/**
* Sets the sort indicators at the table headers,
* only if showSortIndicator_ is true.
* @since version_number
* @author S3460
*/
private void actualizeSortIndicator2UI(){
if(showSortIndicator_){
ComponentClient component = ((PageClient) wModel_.getPage()).getComponent();
TableColumn[] cols = table_.getColumns();
for (int i = 0; i < cols.length; i++) {
TableColumn column = cols[i];
Image image = null;
if(getSortingColumn()>=0&&modelCol2SwtCol_[getSortingColumn()] == i){
if(isSortingColumnAscending()){
image = component.getImage(ARROW_UP_ICON);
}else{
image = component.getImage(ARROW_DOWN_ICON);
}
}else{
image = component.getImage(ARROW_EMPTY_ICON);
}
column.setImage(image);
}
}
}
/**
* @return Returns the ascending.
*/
boolean isSortingColumnAscending() {
return ascending;
}
/**
* @return Returns the sortingCol_.
*/
int getSortingColumn() {
return sortingCol_;
}
/**
* @return Returns the showSortIndicator_.
*/
boolean hasSortIndicator() {
return showSortIndicator_;
}
/**
* @param showSortIndicator_ The showSortIndicator_ to set.
*/
void setSortIndicator(boolean showSortIndicator) {
this.showSortIndicator_ = showSortIndicator;
}
/**
* Set the external sorter which will be used for sorting columns instead of the internal
* sorting implementation of the table.
* @since 3.2.0
* @author gub
*/
void setExternalSorter(TableExternalSorterClient externalSorter) {
externalSorter_ = externalSorter;
}
/**
* Set the RowComparator which will be used for sorting rows
* @param rowComparator
*/
void setRowComparator(RowComparator rowComparator) {
rowComparator_ = rowComparator;
}
/**
* Execute callback.
*
* @param item
*/
protected void callItemCreated(TableItem item) {
PageClient pageClient = (PageClient)wModel_.getPage();
if (pageClient != null) {
pageClient.itemCreated(item);
}
}
/**
* Returns the model's TableRow to the given TableItem.
* @param uiTableItem
* @return TableRow for the given TableItem
* @since version_number
* @author S3460
*/
static TableRow row2Item(TableItem uiTableItem){
Table table = uiTableItem.getParent();
int swtIndex = table.indexOf(uiTableItem);
TableUIDelegateClient uiDelegate = (TableUIDelegateClient) table.getData();
int modelIndex = uiDelegate.swtIndex2ModelIndex(swtIndex);
return uiDelegate.wModel_.getRow(modelIndex);
}
/**
* Deselects all selected items on the widget
*/
protected void deselectAllOnWidget() {
table_.deselectAll();
}
/**
* Sets Selection of SWT widget
*/
protected void setSelectionOnWidget(int[] selIndexes) {
if (selIndexes.length > 0) {
deselectAllOnWidget();
}
table_.setSelection (selIndexes);
updateErrorState();
}
/**
* This method must be called after every change in the SWT control to update
* the error state information. What must be done here is to check the mandatory
* property of the widget model.
*/
void updateErrorState () {
PageClient pageModel = wModel_.getPageModelC();
if (editable_ && wModel_.getFmt() instanceof ASelectionFmt && wModel_.isMandatory()) {
boolean ok = true;
try {
String selectionCount = String.valueOf(table_.getSelectionCount());
wModel_.getFmt().parse(selectionCount);
} catch (AParseException ex) {
// construct a ValidationErrorClient object and register it with the Page
pageModel.setValidationErrorImpl(new ValidationErrorClient (wModel_, getErrorStateWidget(), getLabelText(), ex));
ok = false;
}
if(ok) {
// clear a previous error state
pageModel.clearValidationErrorImpl(getErrorStateWidget());
}
} else {
pageModel.clearValidationErrorImpl(getErrorStateWidget());
}
}
/**
* Widget, that holds error state (the widget that holds the model in data field)
*/
protected Control getErrorStateWidget() {
return table_;
}
/**
* Widget, that indicates the error (e.g. coloring; usually the widget itself)
*/
protected Control getErrorIndicationWidget() {
return table_;
}
/**
* Returns the text of the label which may be null, if the label is unknown.
*/
private String getLabelText () {
return label_ != null ? label_.getText() : null;
}
}