at.spardat.xma.mdl.list.ListDomUIDelegateClient Maven / Gradle / Ivy
/*******************************************************************************
* 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: ListDomUIDelegateClient.java 7135 2011-01-27 00:28:07Z hoenninger $
package at.spardat.xma.mdl.list;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
import java.util.ResourceBundle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TypedEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import at.spardat.enterprise.fmt.FmtParseException;
import at.spardat.xma.boot.logger.LogLevel;
import at.spardat.xma.component.ComponentClient;
import at.spardat.xma.datasource.IDomRow;
import at.spardat.xma.datasource.ITabularDataSource;
import at.spardat.xma.datasource.ITabularDomData;
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.page.EventAdapter;
import at.spardat.xma.page.PageClient;
import at.spardat.xma.plugins.PluginManager;
import at.spardat.xma.security.XMAContext;
import at.spardat.xma.session.XMASession;
import at.spardat.xma.session.XMASessionClient;
/**
* This class is the UIDelegate for a ListDomWMClient widget model. It
* manages the interaction between a SWT-DROP_DOWN-combo and the widget model.
* The attached Combo must be created with the style SWT.DROP_DOWN. The style
* SWT.READ_ONLY must not be set.
*
* Technical footnote#1: A Combo that is not read-only has two states: its text
* (in the text field) and the selectionIndex of the drop-down-list. If the
* selectionIndex is set (differs from -1), the text-field is displayed with
* selected contents if the shell is resized in the current version of SWT.
* Therefore, this class never sets the selectionIndex, but just the text.
* On loose-focus, a selection index, if any, is cleared and the text is set.
*
* @author YSD, 20.04.2003 19:06:19
*/
public class ListDomUIDelegateClient extends UIDelegateClient {
/**
* The attached SWT-control.
*/
private Combo combo_;
/**
* reacts on mouse moves on combo_
*/
private MouseMoveListener mouseMoveListenerOnCombo_;
/**
* The opional label usually positioned at the left hand side of the combo.
*/
private Label label_;
/**
* The widget model.
*/
private ListDomWMClient wModel_;
/**
* Keep track of whether we are updating the UI.
*/
private boolean updatingUI_ = false;
/**
* Holds all valid DomValue objects in the right sort order.
*/
private ArrayList lstValues_;
/**
* Is non null if the first entry in the combo is a key that is
* not in the domain of values itself. This may happen, if the
* selection in the model is out of range. This variable
* then holds the key of this out of domain selection.
*/
private String outOfDomainKey_;
/**
* Holds all outdated DomValue objects.
*/
private ArrayList lstOutDatedValues_;
/**
* The editable-property
*/
private boolean editable_ = true;
/**
* The enabled-property
*/
private boolean enabled_ = true;
/**
* Stores the drop down lists visibleItemCount.
* If a combo model is set to setEditable(false) then the widgets.setVisibleItemCount() is set to 0
* and the old value is stored in this property for resetting the widget's value at setEditable(true).
*/
private int visibleItemCount_;
/**
* Constructor
*/
public ListDomUIDelegateClient (ListDomWMClient wModel) {
wModel_ = wModel;
}
/**
* The following events must be notified from the SWT combo box:
*
* - SelectionEvent
*
- ModifyEvent
*
- FocusEvent for lost focus
*
*
* @see at.spardat.xma.mdl.UIDelegateClient#handleUIEvent(java.lang.Object, int)
*/
public void handleUIEvent (Object event, int type) {
if (!isUIAttached()) return;
if (updatingUI_) {
return;
}
// sanity check: the Widget in the event must be control_
if (event instanceof TypedEvent) {
if (((TypedEvent)event).widget != combo_) {
System.err.println ("widget in event differs from control in UIDelegate; event ignored ...");
return;
}
}
updatingUI_ = true;
try {
if (!editable_) {
/**
* In the case where the combo is not editable, we nevertheless get events since we cannot
* avoid it. But we ignore it and restore the combo to the previous state.
*/
selection2UI();
} else if (event instanceof ModifyEvent) {
/**
* the user modified the text field. This results in a ModifyEvent where
* Combo.getSelectionIndex yields -1. In this case, we do a praefix search
* and update the text to the best matching entry.
*/
if (combo_.getSelectionIndex() == -1) {
selectLongestCommonPraefix();
}
} else if (event instanceof SelectionEvent) {
if (combo_.getSelectionIndex() != -1) {
/**
* the user selected an entry in the combo box list. Here we update the model.
*/
String key = comboSelIndex2Key(combo_.getSelectionIndex());
wModel_.handle(wModel_.new SelectionChangeEvent(key, true));
}
} else if (event instanceof FocusEvent) {
if (combo_.getSelectionIndex() != -1) {
/**
* removes also selection from the combo box, see footnote#1
*/
selection2UI();
}
}
} finally {
updatingUI_ = false;
updateErrorState();
}
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#handleModelChangeEvent(at.spardat.xma.mdl.ModelChangeEvent)
*/
public void handleModelChangeEvent (ModelChangeEvent event) {
// if the UI is not attached, do nothing
if (!isUIAttached()) return;
updatingUI_ = true;
try {
if (event instanceof ListDomWM.SelectionChangedEvent ||
event instanceof ListDomWM.SelectionChangeEvent) {
selection2UI();
} else if (event instanceof ListDomWM.DataSourceChangedEvent) {
// read values
lstValues_ = null;
ensureDataSourceRead();
// fill combo
domain2UI();
// selection
selection2UI();
} else if (event instanceof ListDomWMClient.MandatoryChangedEvent) {
// the mandatory property changed
// here it suffices to call updateErrorState, which is done anyway below.
}
} finally {
updatingUI_ = false;
updateErrorState();
}
}
/**
* @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");
return new Combo(((Composite)parent), SWT.DROP_DOWN|SWT.BORDER);
}
// see at.spardat.xma.mdl.UIDelegateClient.addListeners()
public void addListeners(Object control, EventAdapter adapter) {
if(control instanceof Combo) {
((Combo)control).addFocusListener(adapter);
((Combo)control).addModifyListener(adapter);
((Combo)control).addSelectionListener(adapter);
} else throw new IllegalArgumentException("unsupported control type: "+control.getClass().getName());
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#isUIAttached()
*/
public boolean isUIAttached () {
return combo_ != null;
}
/**
* @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();
boolean attach = false;
if (control instanceof Combo) {
Combo co = (Combo)control;
int style = co.getStyle();
// combo must have the style SWT.DROP_DOWN and must not have the style SWT.READ_ONLY
if ((style & SWT.DROP_DOWN) != 0 && (style & SWT.READ_ONLY) == 0) {
attach = true;
}
combo_ = co;
if (wModel_.isShowLongValueAsTooltips_()) {
// add mousemovelistener on combo
mouseMoveListenerOnCombo_ = new MouseMoveListener () {
public void mouseMove(MouseEvent e) {
adjustToolTip();
}
};
combo_.addMouseMoveListener(mouseMoveListenerOnCombo_);
}
}
if (!attach) {
throw new AttachmentExceptionClient ("ListDomWModel must be attached to a Combo with style SWT.DROP_DOWN and not SWT.READ_ONLY");
}
updatingUI_ = true;
try {
// read values
ensureDataSourceRead();
// fill combo
domain2UI();
// selection
selection2UI();
// save label
if (label != null && label instanceof Label) {
label_ = (Label) label;
}
// reflect editable state on the UI
setEditable (editable_); // just to set the background color
//if the control is already disabled, set this at enabled_ - otherwise do not change enabled_
//as in the Gen class the GUI designer disable property is set directly at the widget (only for some models)
enabled_ = combo_.getEnabled()?enabled_:false;
setEnabled(enabled_); //set the model's enabled state at the control
} finally {
updatingUI_ = false;
updateErrorState();
}
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#getUIControl()
*/
public Object getUIControl () {
if (!isUIAttached()) throw new IllegalStateException ();
return combo_;
}
/**
* @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 () {
updatingUI_ = true;
try {
// clear a previous error state
PageClient pageModel = wModel_.getPageModelC();
pageModel.clearValidationErrorImpl (combo_);
pageModel.clearError(combo_);
pageModel.getDialogPage().clearWarning(combo_);
pageModel.getDialogPage().clearInfo(combo_);
if (mouseMoveListenerOnCombo_ != null) {
combo_.removeMouseMoveListener(mouseMoveListenerOnCombo_);
mouseMoveListenerOnCombo_ = null;
}
enabled_ = combo_.getEnabled();
combo_ = null;
label_ = null;
outOfDomainKey_ = null;
} finally {
updatingUI_ = false;
}
}
/**
* Reads the data source and fills the ArrayList lstValues_. This method
* does nothing if the instance variable lstValues_ is not equal to null.
* The values are also sorted according to the order style of the Widget Model.
*
* Since data is read from a data source, internally, there may abide a bunch of
* exceptions. But this method is deep in the call hierarchy and may also
* be called while synchronizing changes from the server, exceptions are not
* thrown at the moment. The combo box is empty then in the case of failures.
*/
private void ensureDataSourceRead () {
if (lstValues_ != null) return;
lstValues_ = new ArrayList();
lstOutDatedValues_ = new ArrayList();
String dsSpec = wModel_.getDataSource(); // may be null
if (dsSpec != null && dsSpec.length() > 0) {
/**
* there is a non-empty data source specification
*/
XMASession session = ((PageClient)wModel_.getPage()).getComponent().getSession();
PluginManager piManager = session.getPluginManager();
ITabularDataSource dataSourcePlugin = (ITabularDataSource) piManager.getPlugin(ITabularDataSource.class);
// find component
Composite parent = combo_.getParent();
PageClient page = (PageClient) parent.getData();
while (page==null && parent!=null) {
parent = parent.getParent();
page = (PageClient) parent.getData();
}
ITabularDomData data = null;
try {
data = dataSourcePlugin.getDomTable(dsSpec, page.getComponent().getSession());
int size = data.size();
for (int i=0; ilstValues_.
*/
private void domain2UI () {
combo_.removeAll();
outOfDomainKey_ = null;
for (int i=0; i=0; i--) {
DomValue value = (DomValue)lstOutDatedValues_.get(i);
if (value.key_.equals(modelKey)) return value;
}
return null;
}
/**
* This method updates the UI to reflect the selection state of the Widget Model.
* It requires that the domain combo is filled with values and that the data
* is read from the data source.
*/
protected void selection2UI () {
combo_.deselectAll(); // never allow entries to be selected, see footnote#1
String selected = wModel_.getSelected();
if (selected == null) {
// clear selection of the combo
combo_.setText("");
} else {
// selected is here
int indexInValues = indexOfKey (selected);
if (indexInValues != -1) {
// a valid selection where the selected key is part of the domain
int indexInCombo = outOfDomainKey_ == null ? indexInValues : indexInValues + 1;
// combo_.select(indexInCombo); uncommented because of footnote#1
DomValue domVal = (DomValue)lstValues_.get(indexInValues);
combo_.setText(domVal2ComboLine(domVal));
} else {
String useOutOfDomain = wModel_.getPageModelC().getComponent().getSession().getRuntimeProperty("useOutOfDomainKey","true");
if("true".equalsIgnoreCase(useOutOfDomain)) {
// a selection where the value is not part of the domain
// check if an outOfDomain key is already included in the combo
if (outOfDomainKey_ != null) {
// first remove the old one
combo_.remove(0);
outOfDomainKey_ = null;
}
// add a new one to the combo
combo_.add(unknown2ComboLine(selected), 0);
outOfDomainKey_ = selected;
// select it
//combo_.select(0); // uncommented because of footnote#1
combo_.setText(unknown2ComboLine(selected));
} else {
// if outOfDomainKey should not be used
// only show the value in the text field, but not in the drop down box.
DomValue domVal = getOutdated(selected);
if(domVal!=null) {
combo_.setText(domVal2ComboLine(domVal));
} else {
combo_.setText(unknown2ComboLine(selected));
}
}
}
}
}
/**
* Changes the tooltip on every change in this
*/
private void adjustToolTip () {
/**
* change the tooltip to the longvalue if the corresponding flag is set on the model
*/
if (wModel_.isShowLongValueAsTooltips_()) {
String selectedLongValue = null;
String selectedKey = wModel_.getSelected();
if (selectedKey != null) {
int indexInValues = indexOfKey (selectedKey);
if (indexInValues != -1) {
selectedLongValue = ((DomValue)lstValues_.get(indexInValues)).longVal_;
}
}
final String selectedLongValueF = selectedLongValue;
final String oldText = combo_.getToolTipText();
if (!strEqu (oldText, selectedLongValueF)) {
combo_.setToolTipText (selectedLongValueF);
}
}
}
// are the two strings equal; if both Strings are null, they are considered to be equal
private boolean strEqu (String s1, String s2) {
if (s1 == null || s2 == null) return s1 == null && s2 == null;
else return s1.equals (s2);
}
/**
* Yields true, if and only if the long value is shown in the combo.
*/
private boolean showLong () {
return (wModel_.getShowStyle() & ListDomWMClient.SHOW_LONG) != 0;
}
/**
* Yields true, if and only if the short value is shown in the combo.
*/
private boolean showJustShort () {
return (wModel_.getShowStyle() & ListDomWMClient.SHOW_SHORT) != 0;
}
/**
* Searches lstValues_ for a particular key and returns the index if found
* or -1 if not found.
*/
private int indexOfKey (String key) {
for (int i=lstValues_.size()-1; i>=0; i--) {
if (((DomValue)lstValues_.get(i)).key_.equals(key)) return i;
}
return -1;
}
/**
* Retrieves the text of the combo boxes text field and searches for an entry
* in the combo box whose string has a maximum common praefix with the text.
* If found, this entry is selected. Otherwise, the text is cleared.
* The widget model is also kept up to date.
*/
private void selectLongestCommonPraefix () {
int indexMaxCommon = -1;
int maxCommon = 0;
String comboText = combo_.getText();
// stripp off the part after the caret
int newLen = Math.min(combo_.getSelection().y, comboText.length());
comboText = comboText.substring(0, newLen);
if (showLong()) {
// the algorithm if long values are displayed
// here we perform the search on the contents of the combo box
for (int i=0; i maxCommon) {
maxCommon = common;
indexMaxCommon = i;
}
}
} else {
// the algorithm if short (and optionally long) values are displayed
// here we search the ArrayList lstValues
for (int i=0; i maxCommon) {
maxCommon = common;
indexMaxCommon = i;
}
}
// convert the index from a lstValues-index to a combolist index
if (outOfDomainKey_ != null) indexMaxCommon++;
// check for a match with the outOfDomainKey
if (outOfDomainKey_ != null) {
int common = commonPraefixLen (comboText, outOfDomainKey_);
if (common == comboText.length() && common == outOfDomainKey_.length() // exact match
|| common > maxCommon) { // better match
maxCommon = common;
indexMaxCommon = 0;
}
}
}
if (maxCommon == 0) {
// nothing in common --> deselect
wModel_.handle(wModel_.new SelectionChangeEvent(null, true));
selection2UI();
} else {
// the maximum common praefix of len maxCommon is at list index indexMaxCommon
// select this entry
String key = comboSelIndex2Key (indexMaxCommon);
DomValue dv = comboSelIndex2DomValue (indexMaxCommon);
wModel_.handle(wModel_.new SelectionChangeEvent(key, true));
selection2UI();
// correct cursor
int point = maxCommon;
if (!showLong()) {
// when showing the concatination (shortValue - longValue), restrict the caret to the
// the length of the short value
if (dv == null) {
point = Math.min (point, outOfDomainKey_.length());
} else {
point = Math.min (point, dv.shortVal_.length());
}
}
combo_.setSelection(new Point (point, point));
}
}
/**
* Expects a selection index of the combo boxes list and maps this to a key
* in the domain.
*/
private String comboSelIndex2Key (int index) {
DomValue dv = comboSelIndex2DomValue (index);
if (dv == null) return outOfDomainKey_;
return dv.key_;
}
/**
* Expects a selection index of the combo box and maps it to a DomValue object
*
* @param index index returned by combo_.getSelectionIndex()
* @return DomValue object or null if the selection is the outOfDomainKey_
*/
private DomValue comboSelIndex2DomValue (int index) {
if (outOfDomainKey_ != null && index == 0) {
// the key is the outOfDomainKey
return null;
} else {
// an ordinary entry which is also part of the domain
if (outOfDomainKey_ != null) index--;
return (DomValue)(lstValues_.get(index));
}
}
/**
* Returns the length of the common praefix of two Strings, that is the number
* of equal leading characters.
*
* @param s1 the first string. Must not be null.
* @param s2 the second string. Must not be null.
* @return number of equal characters at the front of the two strings.
*/
private int commonPraefixLen (String s1, String s2) {
int len = Math.min(s1.length(), s2.length());
int result = len;
for (int i=0; iinError, this method sets the background of the corresponding Control c
* to the error color defined in ComponentClient. If inError is false,
* the background is reset to the default color.
*/
public void setErrorColor (boolean inError) {
if (editable_) {
if(inError) {
combo_.setBackground (wModel_.getPageModelC().getComponent().getColor(ComponentClient.errorBackground));
} else {
combo_.setBackground(null);
}
} else {
combo_.setBackground (Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
}
}
/**
* Returns the text of the label which may be null, if the label is unknown.
*/
private String getLabelText () {
String text = label_ != null ? label_.getText() : null;
return text;
}
/**
* @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_ = combo_.isEnabled();
}
return enabled_;
}
/**
* @see at.spardat.xma.mdl.UIDelegateClient#setEnabled(boolean)
*/
public void setEnabled (boolean what) {
enabled_ = what;
if (isUIAttached()) combo_.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;
if (isUIAttached()) {
updateErrorState();
int count = combo_.getVisibleItemCount();
if(count>0){
visibleItemCount_ = count;
}
if(!editable_){
combo_.setVisibleItemCount(0);
}else {
combo_.setVisibleItemCount(visibleItemCount_);
}
}
}
/**
* Models a single domain value to be displayed in the combo box.
*
* @author YSD, 22.04.2003 11:17:34
*/
private static class DomValue {
// the key; never null
public String key_;
// the short value; never null
public String shortVal_;
// the long value; never null
public String longVal_;
}
/**
* Comparator to sort the list of DomValues according to the ORDER_-styles of
* class ListDomWMClient.
*
* @author YSD, 22.04.2003 13:11:58
*/
private static class DomValueComparator implements Comparator {
/**
* Constructs a Comparator to sort the list of DomValues
*
* @param orderStyle Order style of class ListDomWMClient
*/
public DomValueComparator (int orderStyle) {
orderStyle_ = orderStyle;
}
/**
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare (Object o1, Object o2) {
DomValue v1 = (DomValue) o1;
DomValue v2 = (DomValue) o2;
if ((orderStyle_ & ListDomWMClient.ORDER_LONG) != 0) {
return v1.longVal_.compareTo(v2.longVal_);
} else {
return v1.shortVal_.compareTo(v2.shortVal_);
}
}
private int orderStyle_;
}
}