/* * 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); } }