/*
* Copyright (C) 2008-2012 OMRON SOFTWARE Co., Ltd.
*
* 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 jp.co.omronsoft.openwnn;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.text.Layout;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.AlignmentSpan;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.HorizontalScrollView;
import android.widget.TextView;
import java.util.ArrayList;
/**
* The default candidates view manager using {@link android.widget.EditText}.
*
* @author Copyright (C) 2011 OMRON SOFTWARE CO., LTD. All Rights Reserved.
*/
public class TextCandidates1LineViewManager extends CandidatesViewManager {
/** displayCandidates() normal display */
private static final int IS_NEXTCANDIDATE_NORMAL = 1;
/** displayCandidates() delay display */
private static final int IS_NEXTCANDIDATE_DELAY = 2;
/** displayCandidates() display end */
private static final int IS_NEXTCANDIDATE_END = 3;
/** Delay of set candidate */
private static final int SET_CANDIDATE_DELAY = 50;
/** Delay Millis */
private static final int CANDIDATE_DELAY_MILLIS = 500;
/** Scroll distance */
private static final float SCROLL_DISTANCE = 0.9f;
/** Body view of the candidates list */
private ViewGroup mViewBody;
/** Scroller */
private HorizontalScrollView mViewBodyScroll;
/** Left more button */
private ImageView mLeftMoreButton;
/** Right more button */
private ImageView mRightMoreButton;
/** Candidate view */
private LinearLayout mViewCandidateList;
/** {@link OpenWnn} instance using this manager */
private OpenWnn mWnn;
/** View type (VIEW_TYPE_NORMAL or VIEW_TYPE_FULL or VIEW_TYPE_CLOSE) */
private int mViewType;
/** view width */
private int mViewWidth;
/** Minimum width of candidate view */
private int mCandidateMinimumWidth;
/** Minimum height of candidate view */
private int mCandidateMinimumHeight;
/** Minimum width of candidate view */
private static final int CANDIDATE_MINIMUM_WIDTH = 48;
/** Whether hide the view if there is no candidate */
private boolean mAutoHideMode;
/** The converter to get candidates from and notice the selected candidate to. */
private WnnEngine mConverter;
/** Limitation of displaying candidates */
private int mDisplayLimit;
/** Vibrator for touch vibration */
private Vibrator mVibrator = null;
/** AudioManager for click sound */
private AudioManager mSound = null;
/** Number of candidates displaying */
private int mWordCount;
/** List of candidates */
private ArrayList<WnnWord> mWnnWordArray;
/** Character width of the candidate area */
private int mLineLength = 0;
/** Maximum width of candidate view */
private int mCandidateMaxWidth = 0;
/** general information about a display */
private final DisplayMetrics mMetrics = new DisplayMetrics();
/** Focus is none now */
private static final int FOCUS_NONE = -1;
/** Handler for set Candidate */
private static final int MSG_SET_CANDIDATES = 1;
/** List of textView for CandiData List */
private ArrayList<TextView> mTextViewArray = new ArrayList<TextView>();
/** Now focus textView index */
private int mCurrentFocusIndex = FOCUS_NONE;
/** Focused View */
private View mFocusedView = null;
/** Focused View Background */
private Drawable mFocusedViewBackground = null;
/** Scale up text size */
private AbsoluteSizeSpan mSizeSpan;
/** Scale up text alignment */
private AlignmentSpan.Standard mCenterSpan;
/** Whether candidates long click enable */
private boolean mEnableCandidateLongClick = true;
/** {@code Handler} Handler for focus Candidate wait delay */
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_CANDIDATES:
displayCandidatesDelay(mConverter);
break;
default:
break;
}
}
};
/** Event listener for touching a candidate */
private OnClickListener mCandidateOnClick = new OnClickListener() {
public void onClick(View v) {
if (!v.isShown()) {
return;
}
playSoundAndVibration();
if (v instanceof CandidateTextView) {
CandidateTextView text = (CandidateTextView)v;
int wordcount = text.getId();
WnnWord word = getWnnWord(wordcount);
clearFocusCandidate();
selectCandidate(word);
}
}
};
/** Event listener for long-clicking a candidate */
private OnLongClickListener mCandidateOnLongClick = new OnLongClickListener() {
public boolean onLongClick(View v) {
if (!v.isShown()) {
return true;
}
if (!mEnableCandidateLongClick) {
return false;
}
clearFocusCandidate();
int wordcount = ((TextView)v).getId();
mWord = mWnnWordArray.get(wordcount);
displayDialog(v, mWord);
return true;
}
};
/**
* Constructor
*/
public TextCandidates1LineViewManager() {
this(300);
}
/**
* Constructor
*
* @param displayLimit The limit of display
*/
public TextCandidates1LineViewManager(int displayLimit) {
mDisplayLimit = displayLimit;
mWnnWordArray = new ArrayList<WnnWord>();
mAutoHideMode = true;
mMetrics.setToDefaults();
}
/**
* Set auto-hide mode.
* @param hide {@code true} if the view will hidden when no candidate exists;
* {@code false} if the view is always shown.
*/
public void setAutoHide(boolean hide) {
mAutoHideMode = hide;
}
/** @see CandidatesViewManager */
public View initView(OpenWnn parent, int width, int height) {
mWnn = parent;
mViewWidth = width;
Resources r = mWnn.getResources();
mCandidateMinimumWidth = (int)(CANDIDATE_MINIMUM_WIDTH * mMetrics.density);
mCandidateMinimumHeight = r.getDimensionPixelSize(R.dimen.candidate_layout_height);
LayoutInflater inflater = parent.getLayoutInflater();
mViewBody = (ViewGroup)inflater.inflate(R.layout.candidates_1line, null);
mViewBodyScroll = (HorizontalScrollView)mViewBody.findViewById(R.id.candview_scroll_1line);
mViewBodyScroll.setOverScrollMode(View.OVER_SCROLL_NEVER);
mViewBodyScroll.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
if (mHandler.hasMessages(MSG_SET_CANDIDATES)) {
mHandler.removeMessages(MSG_SET_CANDIDATES);
mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, CANDIDATE_DELAY_MILLIS);
}
break;
default:
break;
}
return false;
}
});
mLeftMoreButton = (ImageView)mViewBody.findViewById(R.id.left_more_imageview);
mLeftMoreButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (!v.isShown()) {
return;
}
playSoundAndVibration();
if (mViewBodyScroll.getScrollX() > 0) {
mViewBodyScroll.smoothScrollBy(
(int)(mViewBodyScroll.getWidth() * -SCROLL_DISTANCE), 0);
}
}
});
mLeftMoreButton.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View v) {
if (!v.isShown()) {
return false;
}
if (!mViewBodyScroll.fullScroll(View.FOCUS_LEFT)) {
mViewBodyScroll.scrollTo(mViewBodyScroll.getChildAt(0).getWidth(), 0);
}
return true;
}
});
mRightMoreButton = (ImageView)mViewBody.findViewById(R.id.right_more_imageview);
mRightMoreButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (!v.isShown()) {
return;
}
int width = mViewBodyScroll.getWidth();
int scrollMax = mViewBodyScroll.getChildAt(0).getRight();
if ((mViewBodyScroll.getScrollX() + width) < scrollMax) {
mViewBodyScroll.smoothScrollBy((int)(width * SCROLL_DISTANCE), 0);
}
}
});
mRightMoreButton.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View v) {
if (!v.isShown()) {
return false;
}
if (!mViewBodyScroll.fullScroll(View.FOCUS_RIGHT)) {
mViewBodyScroll.scrollTo(0, 0);
}
return true;
}
});
mViewLongPressDialog = (View)inflater.inflate(R.layout.candidate_longpress_dialog, null);
/* select button */
Button longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_select);
longPressDialogButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
playSoundAndVibration();
clearFocusCandidate();
selectCandidate(mWord);
closeDialog();
}
});
/* cancel button */
longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_cancel);
longPressDialogButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
playSoundAndVibration();
mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL));
mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.UPDATE_CANDIDATE));
closeDialog();
}
});
int buttonWidth = r.getDimensionPixelSize(R.dimen.candidate_layout_width);
mCandidateMaxWidth = (mViewWidth - buttonWidth * 2) / 2;
mSizeSpan = new AbsoluteSizeSpan(r.getDimensionPixelSize(R.dimen.candidate_delete_word_size));
mCenterSpan = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER);
createNormalCandidateView();
setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE);
return mViewBody;
}
/**
* Create normal candidate view
*/
private void createNormalCandidateView() {
mViewCandidateList = (LinearLayout)mViewBody.findViewById(R.id.candidates_view_1line);
Context context = mViewBodyScroll.getContext();
for (int i = 0; i < mDisplayLimit; i++) {
mViewCandidateList.addView(new CandidateTextView(context,
mCandidateMinimumHeight,
mCandidateMinimumWidth,
mCandidateMaxWidth));
}
}
/** @see CandidatesViewManager#getCurrentView */
public View getCurrentView() {
return mViewBody;
}
/** @see CandidatesViewManager#setViewType */
public void setViewType(int type) {
mViewType = type;
if (type == CandidatesViewManager.VIEW_TYPE_NORMAL) {
mViewCandidateList.setMinimumHeight(mCandidateMinimumHeight);
} else {
mViewCandidateList.setMinimumHeight(-1);
mHandler.removeMessages(MSG_SET_CANDIDATES);
if (mViewBody.isShown()) {
mWnn.setCandidatesViewShown(false);
}
}
}
/** @see CandidatesViewManager#getViewType */
public int getViewType() {
return mViewType;
}
/** @see CandidatesViewManager#displayCandidates */
public void displayCandidates(WnnEngine converter) {
mHandler.removeMessages(MSG_SET_CANDIDATES);
closeDialog();
clearCandidates();
mConverter = converter;
int isNextCandidate = IS_NEXTCANDIDATE_NORMAL;
while(isNextCandidate == IS_NEXTCANDIDATE_NORMAL) {
isNextCandidate = displayCandidatesNormal(converter);
}
if (isNextCandidate == IS_NEXTCANDIDATE_DELAY) {
isNextCandidate = displayCandidatesDelay(converter);
}
mViewBodyScroll.scrollTo(0,0);
}
/**
* Display the candidates.
* @param converter {@link WnnEngine} which holds candidates.
*/
private int displayCandidatesNormal(WnnEngine converter) {
int isNextCandidate = IS_NEXTCANDIDATE_NORMAL;
if (converter == null) {
return IS_NEXTCANDIDATE_END;
}
/* Get candidates */
WnnWord result = converter.getNextCandidate();
if (result == null) {
return IS_NEXTCANDIDATE_END;
}
mLineLength += setCandidate(result);
if (mLineLength >= mViewWidth) {
isNextCandidate = IS_NEXTCANDIDATE_DELAY;
}
if (mWordCount < 1) { /* no candidates */
if (mAutoHideMode) {
mWnn.setCandidatesViewShown(false);
return IS_NEXTCANDIDATE_END;
}
}
if (mWordCount > mDisplayLimit) {
return IS_NEXTCANDIDATE_END;
}
if (!(mViewBody.isShown())) {
mWnn.setCandidatesViewShown(true);
}
return isNextCandidate;
}
/**
* Display the candidates.
* @param converter {@link WnnEngine} which holds candidates.
*/
private int displayCandidatesDelay(WnnEngine converter) {
int isNextCandidate = IS_NEXTCANDIDATE_DELAY;
if (converter == null) {
return IS_NEXTCANDIDATE_END;
}
/* Get candidates */
WnnWord result = converter.getNextCandidate();
if (result == null) {
return IS_NEXTCANDIDATE_END;
}
setCandidate(result);
if (mWordCount > mDisplayLimit) {
return IS_NEXTCANDIDATE_END;
}
mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, SET_CANDIDATE_DELAY);
return isNextCandidate;
}
/**
* Set the candidate for candidate view
* @param word set word
* @return int Set width
*/
private int setCandidate(WnnWord word) {
CandidateTextView candidateTextView =
(CandidateTextView) mViewCandidateList.getChildAt(mWordCount);
candidateTextView.setCandidateTextView(word, mWordCount, mCandidateOnClick,
mCandidateOnLongClick);
mWnnWordArray.add(mWordCount, word);
mWordCount++;
mTextViewArray.add(candidateTextView);
return candidateTextView.getWidth();
}
/**
* Clear the candidate view
*/
private void clearNormalViewCandidate() {
int candidateNum = mViewCandidateList.getChildCount();
for (int i = 0; i < candidateNum; i++) {
View v = mViewCandidateList.getChildAt(i);
v.setVisibility(View.GONE);
}
}
/** @see CandidatesViewManager#clearCandidates */
public void clearCandidates() {
clearFocusCandidate();
clearNormalViewCandidate();
mLineLength = 0;
mWordCount = 0;
mWnnWordArray.clear();
mTextViewArray.clear();
if (mAutoHideMode && mViewBody.isShown()) {
mWnn.setCandidatesViewShown(false);
}
}
/** @see CandidatesViewManager#setPreferences */
public void setPreferences(SharedPreferences pref) {
try {
if (pref.getBoolean("key_vibration", false)) {
mVibrator = (Vibrator)mWnn.getSystemService(Context.VIBRATOR_SERVICE);
} else {
mVibrator = null;
}
if (pref.getBoolean("key_sound", false)) {
mSound = (AudioManager)mWnn.getSystemService(Context.AUDIO_SERVICE);
} else {
mSound = null;
}
} catch (Exception ex) {
Log.d("OpenWnn", "NO VIBRATOR");
}
}
/**
* Select a candidate.
* <br>
* This method notices the selected word to {@link OpenWnn}.
*
* @param word The selected word
*/
private void selectCandidate(WnnWord word) {
mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.SELECT_CANDIDATE, word));
}
private void playSoundAndVibration() {
if (mVibrator != null) {
try {
mVibrator.vibrate(5);
} catch (Exception ex) {
Log.e("OpenWnn", "TextCandidates1LineViewManager::selectCandidate Vibrator " + ex.toString());
}
}
if (mSound != null) {
try {
mSound.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, 1.0f);
} catch (Exception ex) {
Log.e("OpenWnn", "TextCandidates1LineViewManager::selectCandidate Sound " + ex.toString());
}
}
}
/**
* KeyEvent action for the candidate view.
*
* @param key Key event
*/
public void processMoveKeyEvent(int key) {
if (!mViewBody.isShown()) {
return;
}
switch (key) {
case KeyEvent.KEYCODE_DPAD_LEFT:
moveFocus(-1);
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
moveFocus(1);
break;
case KeyEvent.KEYCODE_DPAD_UP:
moveFocus(-1);
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
moveFocus(1);
break;
default:
break;
}
}
/**
* Get a flag candidate is focused now.
*
* @return the Candidate is focused of a flag.
*/
public boolean isFocusCandidate(){
if (mCurrentFocusIndex != FOCUS_NONE) {
return true;
}
return false;
}
/**
* Give focus to View of candidate.
*/
private void setViewStatusOfFocusedCandidate() {
View view = mFocusedView;
if (view != null) {
view.setBackgroundDrawable(mFocusedViewBackground);
}
TextView v = getFocusedView();
mFocusedView = v;
if (v != null) {
mFocusedViewBackground = v.getBackground();
v.setBackgroundResource(R.drawable.cand_back_focuse);
int viewBodyLeft = getViewLeftOnScreen(mViewBodyScroll);
int viewBodyRight = viewBodyLeft + mViewBodyScroll.getWidth();
int focusedViewLeft = getViewLeftOnScreen(v);
int focusedViewRight = focusedViewLeft + v.getWidth();
if (focusedViewRight > viewBodyRight) {
mViewBodyScroll.scrollBy((focusedViewRight - viewBodyRight), 0);
} else if (focusedViewLeft < viewBodyLeft) {
mViewBodyScroll.scrollBy((focusedViewLeft - viewBodyLeft), 0);
}
}
}
/**
* Clear focus to selected candidate.
*/
private void clearFocusCandidate(){
View view = mFocusedView;
if (view != null) {
view.setBackgroundDrawable(mFocusedViewBackground);
mFocusedView = null;
}
mCurrentFocusIndex = FOCUS_NONE;
mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_END));
}
/**
* Select candidate that has focus.
*/
public void selectFocusCandidate(){
if (mCurrentFocusIndex != FOCUS_NONE) {
selectCandidate(getFocusedWnnWord());
}
}
/**
* Get View of focus candidate.
*/
private TextView getFocusedView() {
if (mCurrentFocusIndex == FOCUS_NONE) {
return null;
}
return mTextViewArray.get(mCurrentFocusIndex);
}
/**
* Move the focus to next candidate.
*
* @param direction The direction of increment or decrement.
*/
private void moveFocus(int direction) {
boolean isStart = (mCurrentFocusIndex == FOCUS_NONE);
int size = mTextViewArray.size();
int index = isStart ? 0 : (mCurrentFocusIndex + direction);
if (index < 0) {
index = size - 1;
} else {
if (index >= size) {
index = 0;
}
}
mCurrentFocusIndex = index;
setViewStatusOfFocusedCandidate();
if (isStart) {
mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_START));
}
}
/**
* Get view top position on screen.
*
* @param view target view.
* @return int view top position on screen
*/
private int getViewLeftOnScreen(View view) {
int[] location = new int[2];
view.getLocationOnScreen(location);
return location[0];
}
/** @see CandidatesViewManager#getFocusedWnnWord */
public WnnWord getFocusedWnnWord() {
return getWnnWord(mCurrentFocusIndex);
}
/**
* Get WnnWord.
*
* @return WnnWord word
*/
public WnnWord getWnnWord(int index) {
return mWnnWordArray.get(index);
}
/** @see CandidatesViewManager#setCandidateMsgRemove */
public void setCandidateMsgRemove() {
mHandler.removeMessages(MSG_SET_CANDIDATES);
}
}