use of com.codename1.rad.ui.UI in project CodenameOne by codenameone.
the class ResetableTextWatcher method edit.
/**
* Entry point for using this class
* @param impl The current running activity
* @param component Any subclass of com.codename1.ui.TextArea
* @param inputType One of the TextArea's input-type constants
*/
public static void edit(final AndroidImplementation impl, final Component component, final int inputType) {
if (impl.getActivity() == null) {
throw new IllegalArgumentException("activity is null");
}
if (component == null) {
throw new IllegalArgumentException("component is null");
}
if (!(component instanceof TextArea)) {
throw new IllegalArgumentException("component must be instance of TextArea");
}
final TextArea textArea = (TextArea) component;
textArea.registerAsInputDevice();
final String initialText = textArea.getText();
textArea.putClientProperty("InPlaceEditView.initialText", initialText);
// The very first time we try to edit a string, let's determine if the
// system default is to do async editing. If the system default
// is not yet set, we set it here, and it will be used as the default from now on
// We do this because the nativeInstance.isAsyncEditMode() value changes
// to reflect the currently edited field so it isn't a good way to keep a
// system default.
String defaultAsyncEditingSetting = Display.getInstance().getProperty("android.VKBAlwaysOpen", null);
if (defaultAsyncEditingSetting == null) {
defaultAsyncEditingSetting = impl.isAsyncEditMode() ? "true" : "false";
Display.getInstance().setProperty("android.VKBAlwaysOpen", defaultAsyncEditingSetting);
}
boolean asyncEdit = "true".equals(defaultAsyncEditingSetting) ? true : false;
// Check if the form has any setting for asyncEditing that should override
// the application defaults.
final Form parentForm = component.getComponentForm();
if (parentForm == null) {
com.codename1.io.Log.p("Attempt to edit text area that is not on a form. This is not supported");
return;
}
if (parentForm.getClientProperty("asyncEditing") != null) {
Object async = parentForm.getClientProperty("asyncEditing");
if (async instanceof Boolean) {
asyncEdit = ((Boolean) async).booleanValue();
// Log.p("Form overriding asyncEdit due to asyncEditing client property: "+asyncEdit);
}
}
if (parentForm.getClientProperty("android.asyncEditing") != null) {
Object async = parentForm.getClientProperty("android.asyncEditing");
if (async instanceof Boolean) {
asyncEdit = ((Boolean) async).booleanValue();
// Log.p("Form overriding asyncEdit due to ios.asyncEditing client property: "+asyncEdit);
}
}
if (parentForm.isFormBottomPaddingEditingMode()) {
asyncEdit = true;
}
// then this will override all other settings.
if (component.getClientProperty("asyncEditing") != null) {
Object async = component.getClientProperty("asyncEditing");
if (async instanceof Boolean) {
asyncEdit = ((Boolean) async).booleanValue();
// Log.p("Overriding asyncEdit due to field asyncEditing client property: "+asyncEdit);
}
}
if (component.getClientProperty("android.asyncEditing") != null) {
Object async = component.getClientProperty("android.asyncEditing");
if (async instanceof Boolean) {
asyncEdit = ((Boolean) async).booleanValue();
// Log.p("Overriding asyncEdit due to field ios.asyncEditing client property: "+asyncEdit);
}
}
final boolean resizeEditMode = "resize".equalsIgnoreCase(String.valueOf(component.getClientProperty("android.editMode")));
final boolean panEditMode = "pan".equalsIgnoreCase(String.valueOf(component.getClientProperty("android.editMode")));
// if true, then in async mode we are currently editing and are switching to another field
final boolean isEditedFieldSwitch;
// If we are already editing, we need to finish that up before we proceed to edit the next field.
synchronized (editingLock) {
if (mIsEditing) {
if (impl.isAsyncEditMode()) {
// Using isEditedFieldSwitch was causing issues with cursors not showing up.
// https://github.com/codenameone/CodenameOne/issues/2353
// https://stackoverflow.com/questions/49004370/focus-behaviour-in-textarea-in-cn1
// Disabling this feature by default now, but can be re-enabled by setting
// Display.getInstance().setProperty("android.reuseTextEditorOnSwitch", "true");
// This editedFieldSwitch feature was added a while back to improve experience on older
// Android devices where the field switching was going too slow.
// https://github.com/codenameone/CodenameOne/issues/2012
// This issue was resolved in this commit (https://github.com/jaanushansen/CodenameOne/commit/f3e53a80704149e4d7cde276d01c1368bcdcfe2c)
// which was submitted as part of a pull request. This fix has been the source of several
// regressions, mostly related to properties not being propagated properly when a text field is changed
// However, this issue (with the cursor not showing up), doesn't appear to have a simple solution
// so, I'm disabling this feature for now.
isEditedFieldSwitch = "true".equals(Display.getInstance().getProperty("android.reuseTextEditorOnSwitch", "false"));
final String[] out = new String[1];
TextArea prevTextArea = null;
if (sInstance != null && sInstance.mLastEditText != null) {
prevTextArea = sInstance.mLastEditText.getTextArea();
}
if (prevTextArea != null) {
final TextArea fPrevTextArea = prevTextArea;
final String retVal = sInstance.mLastEditText.getText().toString();
Display.getInstance().callSerially(new Runnable() {
public void run() {
Display.getInstance().onEditingComplete(fPrevTextArea, retVal);
textArea.requestFocus();
}
});
}
InPlaceEditView.setEditedTextField(textArea);
nextTextArea = null;
} else {
isEditedFieldSwitch = false;
final InPlaceEditView instance = sInstance;
if (instance != null && instance.mEditText != null && instance.mEditText.mTextArea == textArea) {
instance.showTextEditorAgain();
return;
}
if (!isClosing && sInstance != null && sInstance.mEditText != null) {
isClosing = true;
impl.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
instance.endEditing(REASON_UNDEFINED, true, 0);
}
});
}
afterClose = new Runnable() {
@Override
public void run() {
impl.callHideTextEditor();
Display.getInstance().editString(component, textArea.getMaxSize(), inputType, textArea.getText());
}
};
return;
}
} else {
isEditedFieldSwitch = false;
}
mIsEditing = true;
isClosing = false;
afterClose = null;
}
impl.setAsyncEditMode(asyncEdit);
// textArea.setPreferredSize(prefSize);
if (!impl.isAsyncEditMode() && textArea instanceof TextField) {
((TextField) textArea).setEditable(false);
}
final boolean scrollableParent = isScrollableParent(textArea);
// We wrap the text area so that we can safely pass data across to the
// android UI thread.
final TextAreaData textAreaData = new TextAreaData(textArea);
impl.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (!isEditedFieldSwitch) {
releaseEdit();
if (sInstance == null) {
sInstance = new InPlaceEditView(impl);
impl.relativeLayout.addView(sInstance);
}
// Let's try something new here
// We'll ALWAYS try resize edit mode (since it just works better)
// But we'll detect whether the field is still covered by the keyboard
// and switch to pan mode if necessary.
}
if (panEditMode) {
setEditMode(false);
} else if (resizeEditMode) {
setEditMode(true);
} else if (parentForm.isFormBottomPaddingEditingMode()) {
setEditMode(true);
} else if (scrollableParent) {
setEditMode(false);
} else {
trySetEditMode(true);
}
sInstance.startEditing(impl.getActivity(), textAreaData, initialText, inputType, isEditedFieldSwitch);
}
});
final String[] out = new String[1];
// In case the contents of the text area are changed while editing is in progress
// we should propagate the changes to the native text field.
final DataChangedListener textAreaDataChanged = new DataChangedListener() {
@Override
public void dataChanged(int type, int index) {
if (suppressDataChangedEvent) {
// https://github.com/codenameone/CodenameOne/issues/3343
return;
}
TextArea currTextArea = getCurrentTextArea();
if (currTextArea != textArea) {
// This is not the active text area anymore
textArea.removeDataChangedListener(this);
return;
}
final String newText = textArea.getText();
EditView currEditView = getCurrentEditView();
if (currEditView == null || currEditView.mTextArea != textArea) {
textArea.removeDataChangedListener(this);
return;
}
String existingText = currEditView.getText().toString();
// because Objects.equals was not available until API 19
if (!com.codename1.compat.java.util.Objects.equals(newText, existingText)) {
impl.getActivity().runOnUiThread(new Runnable() {
public void run() {
TextArea currTextArea = getCurrentTextArea();
EditView currEditView = getCurrentEditView();
if (currTextArea != textArea || currEditView == null || currEditView.mTextArea != textArea) {
return;
}
String existingText = currEditView.getText().toString();
// because Objects.equals was not available until API 19
if (!com.codename1.compat.java.util.Objects.equals(newText, existingText)) {
// We need to suppress the Android text change events
// to prevent weird things from happening. E.g. https://github.com/codenameone/CodenameOne/issues/3349
suppressTextChangeEvent = true;
currEditView.setText(newText);
suppressTextChangeEvent = false;
}
}
});
}
}
};
textArea.addDataChangedListener(textAreaDataChanged);
// In order to reuse the code the runs after edit completion, we will wrap it in a runnable
// For sync edit mode, we will just run onComplete.run() at the end of this method. For
// Async mode we add the Runnable to the textarea as a client property, then run it
// when editing eventually completes.
Runnable onComplete = new Runnable() {
public void run() {
textArea.removeDataChangedListener(textAreaDataChanged);
if (!impl.isAsyncEditMode() && textArea instanceof TextField) {
((TextField) textArea).setEditable(true);
}
textArea.setPreferredSize(null);
if (sInstance != null && sInstance.mLastEditText != null && sInstance.mLastEditText.mTextArea == textArea) {
String retVal = sInstance.mLastEditText.getText().toString();
if (!impl.isAsyncEditMode()) {
sInstance.mLastEditText = null;
impl.getActivity().runOnUiThread(new Runnable() {
public void run() {
releaseEdit();
}
});
}
out[0] = retVal;
} else {
out[0] = initialText;
}
Display.getInstance().onEditingComplete(component, out[0]);
if (impl.isAsyncEditMode()) {
impl.callHideTextEditor();
} else {
// lock.
if (sInstance != null) {
Display.getInstance().invokeAndBlock(new Runnable() {
public void run() {
while (sInstance != null) {
com.codename1.io.Util.sleep(5);
}
}
});
}
}
// Release the editing flag
synchronized (editingLock) {
mIsEditing = false;
}
// as a runnable ... this should take priority over the "nextTextArea" setting
if (afterClose != null) {
Display.getInstance().callSerially(afterClose);
} else if (nextTextArea != null) {
final TextArea next = nextTextArea;
nextTextArea = null;
next.requestFocus();
Display.getInstance().callSerially(new Runnable() {
public void run() {
Display.getInstance().editString(next, next.getMaxSize(), next.getConstraint(), next.getText());
}
});
}
}
};
textArea.requestFocus();
textArea.repaint();
if (impl.isAsyncEditMode()) {
component.putClientProperty("android.onAsyncEditingComplete", onComplete);
return;
}
// Make this call synchronous
// We set this flag so that waitForEditCompletion can block on it.
// The flag will be released inside the endEditing method which will
// allow the method to proceed.
waitingForSynchronousEditingCompletion = true;
waitForEditCompletion();
onComplete.run();
}
use of com.codename1.rad.ui.UI in project CodenameOne by codenameone.
the class CloudObject method bindTree.
/**
* Binds a UI tree to the cloud object so its values automatically update in the cloud object
*
* @param ui the component tree to bind
* @param defer bind settings whether to defer the binding which requires developers to explicitly commit
* the binding to perform the changes
* @param objectLead if set to true the UI property is initialized from values in the CloudObject, if false
* the cloud object property is initialized from the UI
*/
public void bindTree(Container ui, int defer, boolean objectLead) {
int componentCount = ui.getComponentCount();
for (int iter = 0; iter < componentCount; iter++) {
Component c = ui.getComponentAt(iter);
if (c instanceof Container) {
bindTree((Container) c, defer, objectLead);
continue;
}
String bind = c.getCloudBoundProperty();
if (bind != null && bind.length() > 0) {
String attributeName = c.getCloudDestinationProperty();
if (attributeName != null) {
bindProperty(c, bind, attributeName, defer, objectLead);
}
}
}
}
use of com.codename1.rad.ui.UI in project CodenameOne by codenameone.
the class CloudObject method bindProperty.
/**
* Binds a property value within the given component to this cloud object, this means that
* when the component changes the cloud object changes unless deferred. If the defer flag is
* false all changes are stored in a temporary location and only "committed" once commitBindings()
* is invoked.
* @param cmp the component to bind
* @param propertyName the name of the property in the bound component
* @param attributeName the key within the cloud object
* @param defer bind settings whether to defer the binding which requires developers to explicitly commit
* the binding to perform the changes
* @param objectLead if set to true the UI property is initialized from values in the CloudObject, if false
* the cloud object property is initialized from the UI
*/
public void bindProperty(Component cmp, final String propertyName, final String attributeName, final int defer, boolean objectLead) {
if (objectLead) {
Object val = values.get(attributeName);
Object cmpVal = cmp.getBoundPropertyValue(propertyName);
if (val == null) {
if (cmpVal != null) {
cmp.setBoundPropertyValue(propertyName, null);
}
} else {
if (cmpVal == null || !(val.equals(cmpVal))) {
cmp.setBoundPropertyValue(propertyName, val);
}
}
} else {
Object val = values.get(attributeName);
Object cmpVal = cmp.getBoundPropertyValue(propertyName);
if (cmpVal == null) {
if (val != null) {
values.remove(attributeName);
status = STATUS_MODIFIED;
}
} else {
if (val == null || !(val.equals(cmpVal))) {
values.put(attributeName, cmpVal);
status = STATUS_MODIFIED;
}
}
}
BindTarget target = new BindTarget() {
public void propertyChanged(Component source, String propertyName, Object oldValue, Object newValue) {
switch(defer) {
case BINDING_DEFERRED:
if (deferedValues == null) {
deferedValues = new Hashtable();
}
deferedValues.put(attributeName, newValue);
break;
case BINDING_IMMEDIATE:
values.put(attributeName, newValue);
status = STATUS_MODIFIED;
break;
case BINDING_AUTO_SAVE:
values.put(attributeName, newValue);
status = STATUS_MODIFIED;
CloudStorage.getInstance().save(CloudObject.this);
break;
}
}
};
cmp.bindProperty(propertyName, target);
cmp.putClientProperty("CN1Bind" + propertyName, target);
}
use of com.codename1.rad.ui.UI in project CodenameOne by codenameone.
the class ClearableTextField method wrap.
/**
* Wraps the given text field with a UI that will allow us to clear it
* @param tf the text field
* @param iconSize size in millimeters for the clear icon, -1 for default size
* @return a Container that should be added to the UI instead of the actual text field
*/
public static ClearableTextField wrap(final TextArea tf, float iconSize) {
ClearableTextField cf = new ClearableTextField();
Button b = new Button("", tf.getUIID());
if (iconSize > 0) {
FontImage.setMaterialIcon(b, FontImage.MATERIAL_CLEAR, iconSize);
} else {
FontImage.setMaterialIcon(b, FontImage.MATERIAL_CLEAR);
}
removeCmpBackground(tf);
removeCmpBackground(b);
cf.setUIID(tf.getUIID());
cf.add(BorderLayout.CENTER, tf);
cf.add(BorderLayout.EAST, b);
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
tf.stopEditing();
tf.setText("");
tf.startEditingAsync();
}
});
return cf;
}
use of com.codename1.rad.ui.UI in project CodenameOne by codenameone.
the class InstantUI method createEntryForProperty.
private void createEntryForProperty(PropertyBase b, Container cnt, ArrayList<UiBinding.Binding> allBindings, UiBinding uib) throws RuntimeException {
if (isExcludedProperty(b)) {
return;
}
Class cls = (Class) b.getClientProperty("cn1$cmpCls");
if (cls != null) {
try {
Component cmp = (Component) cls.newInstance();
cmp.setName(b.getName());
cnt.add(b.getLabel()).add(cmp);
allBindings.add(uib.bind(b, cmp));
} catch (Exception err) {
Log.e(err);
throw new RuntimeException("Custom property instant UI failed for " + b.getName() + " " + err);
}
return;
}
String[] multiLabels = (String[]) b.getClientProperty("cn1$multiChceLbl");
if (multiLabels != null) {
// multi choice component
final Object[] multiValues = (Object[]) b.getClientProperty("cn1$multiChceVal");
if (multiLabels.length < 5) {
// toggle buttons
ButtonGroup bg = new ButtonGroup();
RadioButton[] rbs = new RadioButton[multiLabels.length];
cnt.add(b.getLabel());
Container radioBox = new Container(new GridLayout(multiLabels.length));
for (int iter = 0; iter < multiLabels.length; iter++) {
rbs[iter] = RadioButton.createToggle(multiLabels[iter], bg);
radioBox.add(rbs[iter]);
}
cnt.add(radioBox);
allBindings.add(uib.bindGroup(b, multiValues, rbs));
} else {
Picker stringPicker = new Picker();
stringPicker.setStrings(multiLabels);
Map<Object, Object> m1 = new HashMap<Object, Object>();
Map<Object, Object> m2 = new HashMap<Object, Object>();
for (int iter = 0; iter < multiLabels.length; iter++) {
m1.put(multiLabels[iter], multiValues[iter]);
m2.put(multiValues[iter], multiLabels[iter]);
}
cnt.add(b.getLabel()).add(stringPicker);
allBindings.add(uib.bind(b, stringPicker, new UiBinding.PickerAdapter<Object>(new UiBinding.MappingConverter(m1), new UiBinding.MappingConverter(m2))));
}
return;
}
Class t = b.getGenericType();
if (t != null) {
if (t == Boolean.class) {
CheckBox cb = new CheckBox();
uib.bind(b, cb);
cnt.add(b.getLabel()).add(cb);
return;
}
if (t == Date.class) {
Picker dp = new Picker();
dp.setType(Display.PICKER_TYPE_DATE);
uib.bind(b, dp);
cnt.add(b.getLabel()).add(dp);
return;
}
}
TextField tf = new TextField();
tf.setConstraint(getTextFieldConstraint(b));
uib.bind(b, tf);
cnt.add(b.getLabel()).add(tf);
}
Aggregations