1 /* 2 * Copyright (C) 2008-2012 OMRON SOFTWARE Co., Ltd. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package jp.co.omronsoft.openwnn; 17 18 import android.app.AlertDialog; 19 import android.content.Context; 20 import android.content.DialogInterface; 21 import android.content.SharedPreferences; 22 import android.content.res.Resources; 23 import android.graphics.drawable.Drawable; 24 import android.media.AudioManager; 25 import android.os.Handler; 26 import android.os.Message; 27 import android.os.Vibrator; 28 import android.text.Layout; 29 import android.text.SpannableString; 30 import android.text.Spanned; 31 import android.text.style.AbsoluteSizeSpan; 32 import android.text.style.AlignmentSpan; 33 import android.util.DisplayMetrics; 34 import android.util.Log; 35 import android.view.KeyEvent; 36 import android.view.LayoutInflater; 37 import android.view.MotionEvent; 38 import android.view.View; 39 import android.view.ViewGroup; 40 import android.view.View.OnClickListener; 41 import android.view.View.OnLongClickListener; 42 import android.widget.Button; 43 import android.widget.ImageView; 44 import android.widget.LinearLayout; 45 import android.widget.HorizontalScrollView; 46 import android.widget.TextView; 47 48 import java.util.ArrayList; 49 50 /** 51 * The default candidates view manager using {@link android.widget.EditText}. 52 * 53 * @author Copyright (C) 2011 OMRON SOFTWARE CO., LTD. All Rights Reserved. 54 */ 55 public class TextCandidates1LineViewManager extends CandidatesViewManager { 56 57 /** displayCandidates() normal display */ 58 private static final int IS_NEXTCANDIDATE_NORMAL = 1; 59 /** displayCandidates() delay display */ 60 private static final int IS_NEXTCANDIDATE_DELAY = 2; 61 /** displayCandidates() display end */ 62 private static final int IS_NEXTCANDIDATE_END = 3; 63 64 /** Delay of set candidate */ 65 private static final int SET_CANDIDATE_DELAY = 50; 66 /** Delay Millis */ 67 private static final int CANDIDATE_DELAY_MILLIS = 500; 68 69 /** Scroll distance */ 70 private static final float SCROLL_DISTANCE = 0.9f; 71 72 /** Body view of the candidates list */ 73 private ViewGroup mViewBody; 74 /** Scroller */ 75 private HorizontalScrollView mViewBodyScroll; 76 /** Left more button */ 77 private ImageView mLeftMoreButton; 78 /** Right more button */ 79 private ImageView mRightMoreButton; 80 /** Candidate view */ 81 private LinearLayout mViewCandidateList; 82 83 /** {@link OpenWnn} instance using this manager */ 84 private OpenWnn mWnn; 85 /** View type (VIEW_TYPE_NORMAL or VIEW_TYPE_FULL or VIEW_TYPE_CLOSE) */ 86 private int mViewType; 87 88 /** view width */ 89 private int mViewWidth; 90 /** Minimum width of candidate view */ 91 private int mCandidateMinimumWidth; 92 /** Minimum height of candidate view */ 93 private int mCandidateMinimumHeight; 94 95 /** Minimum width of candidate view */ 96 private static final int CANDIDATE_MINIMUM_WIDTH = 48; 97 98 /** Whether hide the view if there is no candidate */ 99 private boolean mAutoHideMode; 100 /** The converter to get candidates from and notice the selected candidate to. */ 101 private WnnEngine mConverter; 102 /** Limitation of displaying candidates */ 103 private int mDisplayLimit; 104 105 /** Vibrator for touch vibration */ 106 private Vibrator mVibrator = null; 107 /** AudioManager for click sound */ 108 private AudioManager mSound = null; 109 110 /** Number of candidates displaying */ 111 private int mWordCount; 112 /** List of candidates */ 113 private ArrayList<WnnWord> mWnnWordArray; 114 115 /** Character width of the candidate area */ 116 private int mLineLength = 0; 117 /** Maximum width of candidate view */ 118 private int mCandidateMaxWidth = 0; 119 /** general information about a display */ 120 private final DisplayMetrics mMetrics = new DisplayMetrics(); 121 /** Focus is none now */ 122 private static final int FOCUS_NONE = -1; 123 /** Handler for set Candidate */ 124 private static final int MSG_SET_CANDIDATES = 1; 125 /** List of textView for CandiData List */ 126 private ArrayList<TextView> mTextViewArray = new ArrayList<TextView>(); 127 /** Now focus textView index */ 128 private int mCurrentFocusIndex = FOCUS_NONE; 129 /** Focused View */ 130 private View mFocusedView = null; 131 /** Focused View Background */ 132 private Drawable mFocusedViewBackground = null; 133 /** Scale up text size */ 134 private AbsoluteSizeSpan mSizeSpan; 135 /** Scale up text alignment */ 136 private AlignmentSpan.Standard mCenterSpan; 137 /** Whether candidates long click enable */ 138 private boolean mEnableCandidateLongClick = true; 139 140 /** {@code Handler} Handler for focus Candidate wait delay */ 141 private Handler mHandler = new Handler() { 142 @Override public void handleMessage(Message msg) { 143 144 switch (msg.what) { 145 case MSG_SET_CANDIDATES: 146 displayCandidatesDelay(mConverter); 147 break; 148 149 default: 150 break; 151 } 152 } 153 }; 154 155 /** Event listener for touching a candidate */ 156 private OnClickListener mCandidateOnClick = new OnClickListener() { 157 public void onClick(View v) { 158 if (!v.isShown()) { 159 return; 160 } 161 playSoundAndVibration(); 162 163 if (v instanceof CandidateTextView) { 164 CandidateTextView text = (CandidateTextView)v; 165 int wordcount = text.getId(); 166 WnnWord word = getWnnWord(wordcount); 167 clearFocusCandidate(); 168 selectCandidate(word); 169 } 170 } 171 }; 172 173 /** Event listener for long-clicking a candidate */ 174 private OnLongClickListener mCandidateOnLongClick = new OnLongClickListener() { 175 public boolean onLongClick(View v) { 176 if (!v.isShown()) { 177 return true; 178 } 179 180 if (!mEnableCandidateLongClick) { 181 return false; 182 } 183 184 clearFocusCandidate(); 185 186 int wordcount = ((TextView)v).getId(); 187 mWord = mWnnWordArray.get(wordcount); 188 189 displayDialog(v, mWord); 190 return true; 191 } 192 }; 193 194 /** 195 * Constructor 196 */ 197 public TextCandidates1LineViewManager() { 198 this(300); 199 } 200 201 /** 202 * Constructor 203 * 204 * @param displayLimit The limit of display 205 */ 206 public TextCandidates1LineViewManager(int displayLimit) { 207 mDisplayLimit = displayLimit; 208 mWnnWordArray = new ArrayList<WnnWord>(); 209 mAutoHideMode = true; 210 mMetrics.setToDefaults(); 211 } 212 213 /** 214 * Set auto-hide mode. 215 * @param hide {@code true} if the view will hidden when no candidate exists; 216 * {@code false} if the view is always shown. 217 */ 218 public void setAutoHide(boolean hide) { 219 mAutoHideMode = hide; 220 } 221 222 /** @see CandidatesViewManager */ 223 public View initView(OpenWnn parent, int width, int height) { 224 mWnn = parent; 225 mViewWidth = width; 226 227 Resources r = mWnn.getResources(); 228 229 mCandidateMinimumWidth = (int)(CANDIDATE_MINIMUM_WIDTH * mMetrics.density); 230 mCandidateMinimumHeight = r.getDimensionPixelSize(R.dimen.candidate_layout_height); 231 232 LayoutInflater inflater = parent.getLayoutInflater(); 233 mViewBody = (ViewGroup)inflater.inflate(R.layout.candidates_1line, null); 234 mViewBodyScroll = (HorizontalScrollView)mViewBody.findViewById(R.id.candview_scroll_1line); 235 mViewBodyScroll.setOverScrollMode(View.OVER_SCROLL_NEVER); 236 mViewBodyScroll.setOnTouchListener(new View.OnTouchListener() { 237 public boolean onTouch(View v, MotionEvent event) { 238 switch (event.getAction()) { 239 case MotionEvent.ACTION_DOWN: 240 case MotionEvent.ACTION_MOVE: 241 if (mHandler.hasMessages(MSG_SET_CANDIDATES)) { 242 mHandler.removeMessages(MSG_SET_CANDIDATES); 243 mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, CANDIDATE_DELAY_MILLIS); 244 } 245 break; 246 247 default: 248 break; 249 250 } 251 return false; 252 } 253 }); 254 255 mLeftMoreButton = (ImageView)mViewBody.findViewById(R.id.left_more_imageview); 256 mLeftMoreButton.setOnClickListener(new View.OnClickListener() { 257 public void onClick(View v) { 258 if (!v.isShown()) { 259 return; 260 } 261 playSoundAndVibration(); 262 if (mViewBodyScroll.getScrollX() > 0) { 263 mViewBodyScroll.smoothScrollBy( 264 (int)(mViewBodyScroll.getWidth() * -SCROLL_DISTANCE), 0); 265 } 266 } 267 }); 268 mLeftMoreButton.setOnLongClickListener(new View.OnLongClickListener() { 269 public boolean onLongClick(View v) { 270 if (!v.isShown()) { 271 return false; 272 } 273 if (!mViewBodyScroll.fullScroll(View.FOCUS_LEFT)) { 274 mViewBodyScroll.scrollTo(mViewBodyScroll.getChildAt(0).getWidth(), 0); 275 } 276 return true; 277 } 278 }); 279 280 mRightMoreButton = (ImageView)mViewBody.findViewById(R.id.right_more_imageview); 281 mRightMoreButton.setOnClickListener(new View.OnClickListener() { 282 public void onClick(View v) { 283 if (!v.isShown()) { 284 return; 285 } 286 int width = mViewBodyScroll.getWidth(); 287 int scrollMax = mViewBodyScroll.getChildAt(0).getRight(); 288 289 if ((mViewBodyScroll.getScrollX() + width) < scrollMax) { 290 mViewBodyScroll.smoothScrollBy((int)(width * SCROLL_DISTANCE), 0); 291 } 292 } 293 }); 294 mRightMoreButton.setOnLongClickListener(new View.OnLongClickListener() { 295 public boolean onLongClick(View v) { 296 if (!v.isShown()) { 297 return false; 298 } 299 if (!mViewBodyScroll.fullScroll(View.FOCUS_RIGHT)) { 300 mViewBodyScroll.scrollTo(0, 0); 301 } 302 return true; 303 } 304 }); 305 306 mViewLongPressDialog = (View)inflater.inflate(R.layout.candidate_longpress_dialog, null); 307 308 /* select button */ 309 Button longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_select); 310 longPressDialogButton.setOnClickListener(new View.OnClickListener() { 311 public void onClick(View v) { 312 playSoundAndVibration(); 313 clearFocusCandidate(); 314 selectCandidate(mWord); 315 closeDialog(); 316 } 317 }); 318 319 /* cancel button */ 320 longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_cancel); 321 longPressDialogButton.setOnClickListener(new View.OnClickListener() { 322 public void onClick(View v) { 323 playSoundAndVibration(); 324 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL)); 325 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.UPDATE_CANDIDATE)); 326 closeDialog(); 327 } 328 }); 329 330 int buttonWidth = r.getDimensionPixelSize(R.dimen.candidate_layout_width); 331 mCandidateMaxWidth = (mViewWidth - buttonWidth * 2) / 2; 332 333 mSizeSpan = new AbsoluteSizeSpan(r.getDimensionPixelSize(R.dimen.candidate_delete_word_size)); 334 mCenterSpan = new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER); 335 336 createNormalCandidateView(); 337 338 setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE); 339 340 return mViewBody; 341 } 342 343 /** 344 * Create normal candidate view 345 */ 346 private void createNormalCandidateView() { 347 mViewCandidateList = (LinearLayout)mViewBody.findViewById(R.id.candidates_view_1line); 348 349 Context context = mViewBodyScroll.getContext(); 350 for (int i = 0; i < mDisplayLimit; i++) { 351 mViewCandidateList.addView(new CandidateTextView(context, 352 mCandidateMinimumHeight, 353 mCandidateMinimumWidth, 354 mCandidateMaxWidth)); 355 } 356 } 357 358 /** @see CandidatesViewManager#getCurrentView */ 359 public View getCurrentView() { 360 return mViewBody; 361 } 362 363 /** @see CandidatesViewManager#setViewType */ 364 public void setViewType(int type) { 365 mViewType = type; 366 367 if (type == CandidatesViewManager.VIEW_TYPE_NORMAL) { 368 mViewCandidateList.setMinimumHeight(mCandidateMinimumHeight); 369 } else { 370 mViewCandidateList.setMinimumHeight(-1); 371 mHandler.removeMessages(MSG_SET_CANDIDATES); 372 373 if (mViewBody.isShown()) { 374 mWnn.setCandidatesViewShown(false); 375 } 376 } 377 } 378 379 /** @see CandidatesViewManager#getViewType */ 380 public int getViewType() { 381 return mViewType; 382 } 383 384 /** @see CandidatesViewManager#displayCandidates */ 385 public void displayCandidates(WnnEngine converter) { 386 387 mHandler.removeMessages(MSG_SET_CANDIDATES); 388 389 closeDialog(); 390 clearCandidates(); 391 mConverter = converter; 392 393 int isNextCandidate = IS_NEXTCANDIDATE_NORMAL; 394 while(isNextCandidate == IS_NEXTCANDIDATE_NORMAL) { 395 isNextCandidate = displayCandidatesNormal(converter); 396 } 397 398 if (isNextCandidate == IS_NEXTCANDIDATE_DELAY) { 399 isNextCandidate = displayCandidatesDelay(converter); 400 } 401 402 mViewBodyScroll.scrollTo(0,0); 403 } 404 405 406 /** 407 * Display the candidates. 408 * @param converter {@link WnnEngine} which holds candidates. 409 */ 410 private int displayCandidatesNormal(WnnEngine converter) { 411 int isNextCandidate = IS_NEXTCANDIDATE_NORMAL; 412 413 if (converter == null) { 414 return IS_NEXTCANDIDATE_END; 415 } 416 417 /* Get candidates */ 418 WnnWord result = converter.getNextCandidate(); 419 if (result == null) { 420 return IS_NEXTCANDIDATE_END; 421 } 422 423 mLineLength += setCandidate(result); 424 if (mLineLength >= mViewWidth) { 425 isNextCandidate = IS_NEXTCANDIDATE_DELAY; 426 } 427 428 if (mWordCount < 1) { /* no candidates */ 429 if (mAutoHideMode) { 430 mWnn.setCandidatesViewShown(false); 431 return IS_NEXTCANDIDATE_END; 432 } 433 } 434 435 if (mWordCount > mDisplayLimit) { 436 return IS_NEXTCANDIDATE_END; 437 } 438 439 if (!(mViewBody.isShown())) { 440 mWnn.setCandidatesViewShown(true); 441 } 442 return isNextCandidate; 443 } 444 445 /** 446 * Display the candidates. 447 * @param converter {@link WnnEngine} which holds candidates. 448 */ 449 private int displayCandidatesDelay(WnnEngine converter) { 450 int isNextCandidate = IS_NEXTCANDIDATE_DELAY; 451 452 if (converter == null) { 453 return IS_NEXTCANDIDATE_END; 454 } 455 456 /* Get candidates */ 457 WnnWord result = converter.getNextCandidate(); 458 if (result == null) { 459 return IS_NEXTCANDIDATE_END; 460 } 461 462 setCandidate(result); 463 464 if (mWordCount > mDisplayLimit) { 465 return IS_NEXTCANDIDATE_END; 466 } 467 468 mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, SET_CANDIDATE_DELAY); 469 470 return isNextCandidate; 471 } 472 473 /** 474 * Set the candidate for candidate view 475 * @param word set word 476 * @return int Set width 477 */ 478 private int setCandidate(WnnWord word) { 479 CandidateTextView candidateTextView = 480 (CandidateTextView) mViewCandidateList.getChildAt(mWordCount); 481 candidateTextView.setCandidateTextView(word, mWordCount, mCandidateOnClick, 482 mCandidateOnLongClick); 483 mWnnWordArray.add(mWordCount, word); 484 mWordCount++; 485 mTextViewArray.add(candidateTextView); 486 487 return candidateTextView.getWidth(); 488 } 489 490 /** 491 * Clear the candidate view 492 */ 493 private void clearNormalViewCandidate() { 494 int candidateNum = mViewCandidateList.getChildCount(); 495 for (int i = 0; i < candidateNum; i++) { 496 View v = mViewCandidateList.getChildAt(i); 497 v.setVisibility(View.GONE); 498 } 499 } 500 501 /** @see CandidatesViewManager#clearCandidates */ 502 public void clearCandidates() { 503 clearFocusCandidate(); 504 clearNormalViewCandidate(); 505 506 mLineLength = 0; 507 508 mWordCount = 0; 509 mWnnWordArray.clear(); 510 mTextViewArray.clear(); 511 512 if (mAutoHideMode && mViewBody.isShown()) { 513 mWnn.setCandidatesViewShown(false); 514 } 515 } 516 517 /** @see CandidatesViewManager#setPreferences */ 518 public void setPreferences(SharedPreferences pref) { 519 try { 520 if (pref.getBoolean("key_vibration", false)) { 521 mVibrator = (Vibrator)mWnn.getSystemService(Context.VIBRATOR_SERVICE); 522 } else { 523 mVibrator = null; 524 } 525 if (pref.getBoolean("key_sound", false)) { 526 mSound = (AudioManager)mWnn.getSystemService(Context.AUDIO_SERVICE); 527 } else { 528 mSound = null; 529 } 530 } catch (Exception ex) { 531 Log.d("OpenWnn", "NO VIBRATOR"); 532 } 533 } 534 535 /** 536 * Select a candidate. 537 * <br> 538 * This method notices the selected word to {@link OpenWnn}. 539 * 540 * @param word The selected word 541 */ 542 private void selectCandidate(WnnWord word) { 543 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.SELECT_CANDIDATE, word)); 544 } 545 546 private void playSoundAndVibration() { 547 if (mVibrator != null) { 548 try { 549 mVibrator.vibrate(5); 550 } catch (Exception ex) { 551 Log.e("OpenWnn", "TextCandidates1LineViewManager::selectCandidate Vibrator " + ex.toString()); 552 } 553 } 554 if (mSound != null) { 555 try { 556 mSound.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, 1.0f); 557 } catch (Exception ex) { 558 Log.e("OpenWnn", "TextCandidates1LineViewManager::selectCandidate Sound " + ex.toString()); 559 } 560 } 561 } 562 563 /** 564 * KeyEvent action for the candidate view. 565 * 566 * @param key Key event 567 */ 568 public void processMoveKeyEvent(int key) { 569 if (!mViewBody.isShown()) { 570 return; 571 } 572 573 switch (key) { 574 case KeyEvent.KEYCODE_DPAD_LEFT: 575 moveFocus(-1); 576 break; 577 578 case KeyEvent.KEYCODE_DPAD_RIGHT: 579 moveFocus(1); 580 break; 581 582 case KeyEvent.KEYCODE_DPAD_UP: 583 moveFocus(-1); 584 break; 585 586 case KeyEvent.KEYCODE_DPAD_DOWN: 587 moveFocus(1); 588 break; 589 590 default: 591 break; 592 593 } 594 } 595 596 /** 597 * Get a flag candidate is focused now. 598 * 599 * @return the Candidate is focused of a flag. 600 */ 601 public boolean isFocusCandidate(){ 602 if (mCurrentFocusIndex != FOCUS_NONE) { 603 return true; 604 } 605 return false; 606 } 607 608 /** 609 * Give focus to View of candidate. 610 */ 611 private void setViewStatusOfFocusedCandidate() { 612 View view = mFocusedView; 613 if (view != null) { 614 view.setBackgroundDrawable(mFocusedViewBackground); 615 } 616 617 TextView v = getFocusedView(); 618 mFocusedView = v; 619 if (v != null) { 620 mFocusedViewBackground = v.getBackground(); 621 v.setBackgroundResource(R.drawable.cand_back_focuse); 622 623 int viewBodyLeft = getViewLeftOnScreen(mViewBodyScroll); 624 int viewBodyRight = viewBodyLeft + mViewBodyScroll.getWidth(); 625 int focusedViewLeft = getViewLeftOnScreen(v); 626 int focusedViewRight = focusedViewLeft + v.getWidth(); 627 628 if (focusedViewRight > viewBodyRight) { 629 mViewBodyScroll.scrollBy((focusedViewRight - viewBodyRight), 0); 630 } else if (focusedViewLeft < viewBodyLeft) { 631 mViewBodyScroll.scrollBy((focusedViewLeft - viewBodyLeft), 0); 632 } 633 } 634 } 635 636 /** 637 * Clear focus to selected candidate. 638 */ 639 private void clearFocusCandidate(){ 640 View view = mFocusedView; 641 if (view != null) { 642 view.setBackgroundDrawable(mFocusedViewBackground); 643 mFocusedView = null; 644 } 645 646 mCurrentFocusIndex = FOCUS_NONE; 647 648 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_END)); 649 } 650 651 /** 652 * Select candidate that has focus. 653 */ 654 public void selectFocusCandidate(){ 655 if (mCurrentFocusIndex != FOCUS_NONE) { 656 selectCandidate(getFocusedWnnWord()); 657 } 658 } 659 660 /** 661 * Get View of focus candidate. 662 */ 663 private TextView getFocusedView() { 664 if (mCurrentFocusIndex == FOCUS_NONE) { 665 return null; 666 } 667 return mTextViewArray.get(mCurrentFocusIndex); 668 } 669 670 /** 671 * Move the focus to next candidate. 672 * 673 * @param direction The direction of increment or decrement. 674 */ 675 private void moveFocus(int direction) { 676 boolean isStart = (mCurrentFocusIndex == FOCUS_NONE); 677 int size = mTextViewArray.size(); 678 int index = isStart ? 0 : (mCurrentFocusIndex + direction); 679 680 if (index < 0) { 681 index = size - 1; 682 } else { 683 if (index >= size) { 684 index = 0; 685 } 686 } 687 688 mCurrentFocusIndex = index; 689 setViewStatusOfFocusedCandidate(); 690 691 if (isStart) { 692 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_START)); 693 } 694 } 695 696 /** 697 * Get view top position on screen. 698 * 699 * @param view target view. 700 * @return int view top position on screen 701 */ 702 private int getViewLeftOnScreen(View view) { 703 int[] location = new int[2]; 704 view.getLocationOnScreen(location); 705 return location[0]; 706 } 707 708 /** @see CandidatesViewManager#getFocusedWnnWord */ 709 public WnnWord getFocusedWnnWord() { 710 return getWnnWord(mCurrentFocusIndex); 711 } 712 713 /** 714 * Get WnnWord. 715 * 716 * @return WnnWord word 717 */ 718 public WnnWord getWnnWord(int index) { 719 return mWnnWordArray.get(index); 720 } 721 722 /** @see CandidatesViewManager#setCandidateMsgRemove */ 723 public void setCandidateMsgRemove() { 724 mHandler.removeMessages(MSG_SET_CANDIDATES); 725 } 726 } 727