/*
** Copyright 2010, The LimeIME Open Source Project
**
** Project Url: http://code.google.com/p/limeime/
** http://android.toload.net/
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.toload.main.hd.keyboard;
import net.toload.main.hd.LIMEKeyboardSwitcher;
import net.toload.main.hd.R;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Paint.Align;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.Log;
import android.view.ViewConfiguration;
import android.view.inputmethod.EditorInfo;
/**
* @author Art Hung
*/
public class LIMEKeyboard extends LIMEBaseKeyboard {
static final boolean DEBUG = false;
static final String TAG = "LIMEKeyboard";
private Drawable mShiftLockIcon;
private Drawable mShiftLockPreviewIcon;
private Drawable mOldShiftIcon;
private Drawable mOldShiftPreviewIcon;
private Key mShiftKey;
private Key mEnterKey;
private static final int SHIFT_OFF = 0;
private static final int SHIFT_ON = 1;
private static final int SHIFT_LOCKED = 2;
private int mShiftState = SHIFT_OFF;
private static final float SPACEBAR_DRAG_THRESHOLD = 0.6f;
private SlidingSpaceBarDrawable mSlidingSpaceBarIcon;
private Drawable mSpacePreviewIcon;
private static int sSpacebarVerticalCorrection;
private boolean mCurrentlyInSpace;
private int mSpaceDragStartX;
private int mSpaceDragLastDiff;
private Key mSpaceKey;
// Minimum width of space key preview (proportional to keyboard width)
private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f;
// Height in space key the language name will be drawn. (proportional to space key height)
private static final float SPACEBAR_LANGUAGE_BASELINE = 0.6f;
private static final int OPACITY_FULLY_OPAQUE = 255;
private final Context mContext;
private final Resources mRes;
//private final int mMode;
private LIMEKeyboardSwitcher mKeyboardSwitcher;
// public LIMEKeyboard(Context context, int xmlLayoutResId) {
// this(context, xmlLayoutResId, 0, 1, false);
// }
public LIMEKeyboard(Context context, int xmlLayoutResId, int mode, float keySizeScale, int showArrowKeys, int splitKeyboard ) {
super(context, xmlLayoutResId, mode, keySizeScale, showArrowKeys, splitKeyboard);
final Resources res = context.getResources();
mContext = context;
mRes = res;
mShiftLockIcon = res.getDrawable(R.drawable.sym_keyboard_shift_locked);
mShiftLockPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_shift_locked);
mShiftLockPreviewIcon.setBounds(0, 0,
mShiftLockPreviewIcon.getIntrinsicWidth(),
mShiftLockPreviewIcon.getIntrinsicHeight());
sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
R.dimen.spacebar_vertical_correction);
mSpacePreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_space);
}
@Override
protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
XmlResourceParser parser) {
Key key = new LIMEKey(res, parent, x, y, parser);
switch (key.codes[0]) {
case KEYCODE_ENTER:
mEnterKey = key;
break;
case KEYCODE_SPACE:
mSpaceKey = key;
break;
}
// 09/Aug 2011, by redraw the key to construct the customer icon set.
// Getting Customer ICON SET
// key.icon = new CustomDrawable("A");
return key;
}
public void enableShiftLock() {
int index = getShiftKeyIndex();
if (index >= 0) {
mShiftKey = getKeys().get(index);
if (mShiftKey instanceof LIMEKey) {
((LIMEKey)mShiftKey).enableShiftLock();
}
mOldShiftIcon = mShiftKey.icon;
mOldShiftPreviewIcon = mShiftKey.iconPreview;
}
}
public void setShiftLocked(boolean shiftLocked) {
if(DEBUG) {Log.i("LIMEKeyboard", "setShiftLocked: "+ shiftLocked);};
if (mShiftKey != null) {
if (shiftLocked) {
mShiftKey.on = true;
mShiftKey.icon = mShiftLockIcon;
mShiftState = SHIFT_LOCKED;
} else {
mShiftKey.on = false;
mShiftKey.icon = mShiftLockIcon;
mShiftState = SHIFT_ON;
}
}
}
public boolean isShiftLocked() {
return mShiftState == SHIFT_LOCKED;
}
@Override
public boolean setShifted(boolean shiftState) {
if(DEBUG) {Log.i("LIMEKeyboard", "setShifted: "+ shiftState);};
boolean shiftChanged = false;
if (mShiftKey != null) {
if (shiftState == false) {
shiftChanged = mShiftState != SHIFT_OFF;
mShiftState = SHIFT_OFF;
mShiftKey.on = false;
mShiftKey.icon = mOldShiftIcon;
mShiftKey.iconPreview = mOldShiftPreviewIcon;
} else {
if (mShiftState == SHIFT_OFF) {
shiftChanged = mShiftState == SHIFT_OFF;
mShiftState = SHIFT_ON;
mShiftKey.icon = mShiftLockIcon;
}
}
} else {
return super.setShifted(shiftState);
}
return shiftChanged;
}
@Override
public boolean isShifted() {
if (mShiftKey != null) {
return mShiftState != SHIFT_OFF;
} else {
return super.isShifted();
}
}
void setImeOptions(Resources res, int options) {
setImeOptions(res, LIMEKeyboardSwitcher.MODE_TEXT, options);
}
public void setImeOptions(Resources res, int mode, int options) {
if (mEnterKey != null) {
// Reset some of the rarely used attributes.
mEnterKey.popupCharacters = null;
mEnterKey.popupResId = 0;
mEnterKey.text = null;
switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) {
case EditorInfo.IME_ACTION_GO:
mEnterKey.iconPreview = null;
mEnterKey.icon = null;
mEnterKey.label = res.getText(R.string.label_go_key);
break;
case EditorInfo.IME_ACTION_NEXT:
mEnterKey.iconPreview = null;
mEnterKey.icon = null;
//int c[] = {-99};
//mEnterKey.codes = c;
mEnterKey.label = res.getText(R.string.label_next_key);
break;
case EditorInfo.IME_ACTION_DONE:
mEnterKey.iconPreview = null;
mEnterKey.icon = null;
mEnterKey.label = res.getText(R.string.label_done_key);
break;
case EditorInfo.IME_ACTION_SEARCH:
mEnterKey.iconPreview = res.getDrawable(
R.drawable.sym_keyboard_feedback_search);
mEnterKey.icon = res.getDrawable(
R.drawable.sym_keyboard_search);
mEnterKey.label = null;
break;
case EditorInfo.IME_ACTION_SEND:
mEnterKey.iconPreview = null;
mEnterKey.icon = null;
mEnterKey.label = res.getText(R.string.label_send_key);
break;
default:
if (mode == LIMEKeyboardSwitcher.MODE_IM) {
mEnterKey.icon = null;
mEnterKey.iconPreview = null;
mEnterKey.label = ":-)";
//mEnterKey.text = ":-) ";
mEnterKey.popupResId = R.xml.popup_smileys;
} else {
mEnterKey.iconPreview = res.getDrawable(
R.drawable.sym_keyboard_feedback_return);
mEnterKey.icon = res.getDrawable(
R.drawable.sym_keyboard_return);
mEnterKey.label = null;
}
break;
}
// Set the initial size of the preview icon
if (mEnterKey.iconPreview != null) {
mEnterKey.iconPreview.setBounds(0, 0,
mEnterKey.iconPreview.getIntrinsicWidth(),
mEnterKey.iconPreview.getIntrinsicHeight());
}
}
}
/**
* Does the magic of locking the touch gesture into the spacebar when
* switching input languages.
*/
boolean isInside(LIMEKey key, int x, int y) {
if(DEBUG) Log.i(TAG, "isInside(), keycode = " + key.codes[0] + ". x=" + x + ". y="+y +
". mSpaceDragStartX=" + mSpaceDragStartX +
". mSpaceDragLastDiff=" + mSpaceDragLastDiff);
final int code = key.codes[0];
if (code == KEYCODE_SHIFT ||
code == KEYCODE_DELETE) {
y -= key.height / 10;
if (code == KEYCODE_SHIFT) x += key.width / 6;
if (code == KEYCODE_DELETE) x -= key.width / 6;
} else if (code == KEYCODE_SPACE) {
y += LIMEKeyboard.sSpacebarVerticalCorrection;
if (mCurrentlyInSpace) {
int diff = x - mSpaceDragStartX;
if (Math.abs(diff - mSpaceDragLastDiff) > 0) {
updateSpacebarDrag(diff);
}
mSpaceDragLastDiff = diff;
return true;
} else {
boolean insideSpace = key.isInsideSuper(x, y);
if (insideSpace) {
mCurrentlyInSpace = true;
mSpaceDragStartX = x;
updateSpacebarDrag(0);
}
return insideSpace;
}
}
// Lock into the spacebar
if (mCurrentlyInSpace) return false;
return key.isInsideSuper(x, y);
}
void keyReleased() {
mCurrentlyInSpace = false;
mSpaceDragLastDiff = 0;
if (mSpaceKey != null) {
updateSpacebarDrag(Integer.MAX_VALUE);
}
}
public int getSpaceDragDiff() {
return mSpaceDragLastDiff;
}
public int getSpaceDragDirection() {
if(DEBUG) Log.i(TAG, "getSpaceDragDirection(): mSpaceDragLastDiff= " +
mSpaceDragLastDiff + ". mSpaceKey.width=" + mSpaceKey.width);
if (mSpaceKey == null
|| Math.abs(mSpaceDragLastDiff) < mSpaceKey.width * SPACEBAR_DRAG_THRESHOLD ) {
return 0; // No change
}
return mSpaceDragLastDiff > 0 ? 1 : -1;
}
/* private int getTextSizeFromTheme(int style, int defValue) {
TypedArray array = mContext.getTheme().obtainStyledAttributes(
style, new int[] { android.R.attr.textSize });
int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue);
return textSize;
}*/
private void setDefaultBounds(Drawable drawable) {
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
}
private void updateSpacebarDrag(int diff) {
if(DEBUG) Log.i(TAG, "updateSpacebarDrag(), deff=" + diff);
if (mSlidingSpaceBarIcon == null) {
final int width = Math.max(mSpaceKey.width,
(int)(getMinWidth() * SPACEBAR_POPUP_MIN_RATIO));
final int height = mSpacePreviewIcon.getIntrinsicHeight();
mSlidingSpaceBarIcon = new SlidingSpaceBarDrawable(mSpacePreviewIcon, width, height);
mSlidingSpaceBarIcon.setBounds(0, 0, width, height);
mSpaceKey.iconPreview = mSlidingSpaceBarIcon;
}
mSlidingSpaceBarIcon.setDiff(diff);
if (Math.abs(diff) == Integer.MAX_VALUE) {
mSpaceKey.iconPreview = mSpacePreviewIcon;
} else {
mSpaceKey.iconPreview = mSlidingSpaceBarIcon;
}
mSpaceKey.iconPreview.invalidateSelf();
}
class LIMEKey extends LIMEBaseKeyboard.Key {
private boolean mShiftLockEnabled;
//Jeremy '12,5,22 moved to LIMEBaseKeyboard
// functional normal state (with properties)
/*private final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
android.R.attr.state_single
};
// functional pressed state (with properties)
private final int[] KEY_STATE_FUNCTIONAL_PRESSED = {
android.R.attr.state_single,
android.R.attr.state_pressed
};*/
/* private boolean isFunctionalKey() {
return !sticky && modifier;
}
@Override
public int[] getCurrentDrawableState() {
if (isFunctionalKey()) {
if (pressed) {
return KEY_STATE_FUNCTIONAL_PRESSED;
} else {
return KEY_STATE_FUNCTIONAL_NORMAL;
}
}
return super.getCurrentDrawableState();
}*/
public LIMEKey(Resources res, LIMEBaseKeyboard.Row parent, int x, int y, XmlResourceParser parser) {
super(res, parent, x, y, parser);
if(DEBUG) Log.i(TAG,"LIMEKey():"+this.codes[0]);
if (popupCharacters != null && popupCharacters.length() == 0) {
// If there is a keyboard with no keys specified in popupCharacters
popupResId = 0;
}
// TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser), R.styleable.LIMEKey);
// CharSequence testAtttribute = a.getText(R.styleable.LIMEKey_testAttribute);
// a.recycle();
// if(testAtttribute!=null)
// Log.i(TAG,"LIMEKey(): Got test attribute:" +testAtttribute );
}
@Override
public void onReleased(boolean inside) {
if (!mShiftLockEnabled) {
super.onReleased(inside);
} else {
pressed = !pressed;
}
}
void enableShiftLock() {
mShiftLockEnabled = true;
}
/**
* Overriding this method so that we can reduce the target area for the key that
* closes the keyboard.
*/
@Override
public boolean isInside(int x, int y) {
// final int code = codes[0];
// if (code == KEYCODE_SHIFT ||
// code == KEYCODE_DELETE) {
// y -= height / 10;
// if (code == KEYCODE_SHIFT) x += width / 6;
// if (code == KEYCODE_DELETE) x -= width / 6;
// }
// if (code == KEYCODE_CANCEL) y -= 10;
return LIMEKeyboard.this.isInside(this, x, y);
//return super.isInside(x, y);
}
boolean isInsideSuper(int x, int y) {
return super.isInside(x, y);
}
}
/**
*
* Jeremy '11,8,5 make a link back channel to LIMEKeyboardSwitcher
*/
public void setKeyboardSwitcher(LIMEKeyboardSwitcher keyboardswitcher){
mKeyboardSwitcher = keyboardswitcher;
}
/**
* Animation to be displayed on the spacebar preview popup when switching
* IM by swiping the spacebar. It draws the current, previous and
* next languages and moves them by the delta of touch movement on the spacebar.
*/
class SlidingSpaceBarDrawable extends Drawable {
private final int mWidth;
private final int mHeight;
private final Drawable mBackground;
private final TextPaint mTextPaint;
private final int mMiddleX;
private final Drawable mLeftDrawable;
private final Drawable mRightDrawable;
private final int mThreshold;
private int mDiff;
private boolean mHitThreshold;
private String mCurrentKeyboard;
private String mNextKeyboard;
private String mPrevKeyboard;
public SlidingSpaceBarDrawable(Drawable background, int width, int height) {
mBackground = background;
setDefaultBounds(mBackground);
mWidth = width;
mHeight = height;
mTextPaint = new TextPaint();
//mTextPaint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Medium, 18));
mTextPaint.setTextSize(mRes.getDimensionPixelSize(R.dimen.spacebar_preview_text_size));
int color = mContext.getResources().getColor((R.color.limekeyboard_transparent));
mTextPaint.setColor(color);
mTextPaint.setTextAlign(Align.CENTER);
mTextPaint.setAlpha(OPACITY_FULLY_OPAQUE);
mTextPaint.setAntiAlias(true);
mMiddleX = (mWidth - mBackground.getIntrinsicWidth()) / 2;
mLeftDrawable =
mRes.getDrawable(R.drawable.ic_suggest_strip_scroll_left_arrow);
mRightDrawable =
mRes.getDrawable(R.drawable.ic_suggest_strip_scroll_right_arrow);
mThreshold = ViewConfiguration.get(mContext).getScaledTouchSlop();
}
private void setDiff(int diff) {
if(DEBUG) Log.i(TAG, "setDiff()");
if (diff == Integer.MAX_VALUE) {
mHitThreshold = false;
mCurrentKeyboard = null;
return;
}
mDiff = diff;
if (mDiff > mWidth) mDiff = mWidth;
if (mDiff < -mWidth) mDiff = -mWidth;
if (Math.abs(mDiff) > mThreshold) mHitThreshold = true;
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
canvas.save();
if (mHitThreshold) {
Paint paint = mTextPaint;
final int width = mWidth;
final int height = mHeight;
final int diff = mDiff;
final Drawable lArrow = mLeftDrawable;
final Drawable rArrow = mRightDrawable;
canvas.clipRect(0, 0, width, height);
if (mCurrentKeyboard == null) {
mCurrentKeyboard = mKeyboardSwitcher.getActiveIMShortname();
mNextKeyboard = mKeyboardSwitcher.getNextActivatedIMShortname();
mPrevKeyboard = mKeyboardSwitcher.getPrevActivatedIMShortname();
if(DEBUG) Log.i(TAG, "SlidingSpaceBarDrawable:draw(), current=" + mCurrentKeyboard +
". next = " + mNextKeyboard + ". prev = " + mPrevKeyboard);
}
// Draw language text with shadow
final float baseline = mHeight * SPACEBAR_LANGUAGE_BASELINE - paint.descent();
paint.setColor(mRes.getColor(R.color.limekeyboard_key_color_black));
paint.setTextSize(mRes.getDimensionPixelSize(R.dimen.spacebar_preview_text_size));
canvas.drawText(mCurrentKeyboard, width / 2 + diff, baseline, paint);
canvas.drawText(mNextKeyboard, diff - width /5, baseline, paint);
canvas.drawText(mPrevKeyboard, diff + width + width/5, baseline, paint);
setDefaultBounds(lArrow);
rArrow.setBounds(width - rArrow.getIntrinsicWidth(), 0, width,
rArrow.getIntrinsicHeight());
lArrow.draw(canvas);
rArrow.draw(canvas);
}
if (mBackground != null) {
canvas.translate(mMiddleX, 0);
mBackground.draw(canvas);
}
canvas.restore();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) {
// Ignore
}
@Override
public void setColorFilter(ColorFilter cf) {
// Ignore
}
@Override
public int getIntrinsicWidth() {
return mWidth;
}
@Override
public int getIntrinsicHeight() {
return mHeight;
}
}
}