1 /* 2 * Copyright (C) 2008,2009 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 17 package jp.co.omronsoft.openwnn; 18 19 import java.util.ArrayList; 20 import android.content.Context; 21 import android.content.SharedPreferences; 22 import android.content.res.Configuration; 23 import android.content.res.Resources; 24 import android.media.MediaPlayer; 25 import android.os.Vibrator; 26 import android.text.TextUtils; 27 import android.text.TextPaint; 28 import android.text.SpannableString; 29 import android.text.Spanned; 30 import android.text.style.ImageSpan; 31 import android.text.style.DynamicDrawableSpan; 32 import android.util.Log; 33 import android.util.DisplayMetrics; 34 import android.view.Gravity; 35 import android.view.MotionEvent; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.view.View.OnClickListener; 39 import android.view.View.OnLongClickListener; 40 import android.view.View.OnTouchListener; 41 import android.view.GestureDetector; 42 import android.view.LayoutInflater; 43 import android.widget.Button; 44 import android.widget.LinearLayout; 45 import android.widget.ScrollView; 46 import android.widget.TextView; 47 import android.widget.EditText; 48 import android.widget.RelativeLayout; 49 import android.widget.ImageView; 50 import android.graphics.drawable.Drawable; 51 52 /** 53 * The default candidates view manager class using {@link EditText}. 54 * 55 * @author Copyright (C) 2009 OMRON SOFTWARE CO., LTD. All Rights Reserved. 56 */ 57 public class TextCandidatesViewManager implements CandidatesViewManager, GestureDetector.OnGestureListener { 58 /** Height of a line */ 59 public static final int LINE_HEIGHT = 34; 60 /** Number of lines to display (Portrait) */ 61 public static final int LINE_NUM_PORTRAIT = 2; 62 /** Number of lines to display (Landscape) */ 63 public static final int LINE_NUM_LANDSCAPE = 1; 64 65 /** Maximum lines */ 66 private static final int DISPLAY_LINE_MAX_COUNT = 1000; 67 /** Width of the view */ 68 private static final int CANDIDATE_MINIMUM_WIDTH = 48; 69 /** Height of the view */ 70 private static final int CANDIDATE_MINIMUM_HEIGHT = 35; 71 /** Align the candidate left if the width of the string exceeds this threshold */ 72 private static final int CANDIDATE_LEFT_ALIGN_THRESHOLD = 120; 73 /** Maximum number of displaying candidates par one line (full view mode) */ 74 private static final int FULL_VIEW_DIV = 4; 75 76 /** Body view of the candidates list */ 77 private ViewGroup mViewBody; 78 /** Scroller of {@code mViewBodyText} */ 79 private ScrollView mViewBodyScroll; 80 /** Base of {@code mViewCandidateList1st}, {@code mViewCandidateList2nd} */ 81 private ViewGroup mViewCandidateBase; 82 /** Button displayed bottom of the view when there are more candidates. */ 83 private ImageView mReadMoreButton; 84 /** The view of the scaling up candidate */ 85 private View mViewScaleUp; 86 /** Layout for the candidates list on normal view */ 87 private LinearLayout mViewCandidateList1st; 88 /** Layout for the candidates list on full view */ 89 private RelativeLayout mViewCandidateList2nd; 90 /** {@link OpenWnn} instance using this manager */ 91 private OpenWnn mWnn; 92 /** View type (VIEW_TYPE_NORMAL or VIEW_TYPE_FULL or VIEW_TYPE_CLOSE) */ 93 private int mViewType; 94 /** Portrait display({@code true}) or landscape({@code false}) */ 95 private boolean mPortrait; 96 97 /** Width of the view */ 98 private int mViewWidth; 99 /** Height of the view */ 100 private int mViewHeight; 101 /** Whether hide the view if there is no candidates */ 102 private boolean mAutoHideMode; 103 /** The converter to be get candidates from and notice the selected candidate to. */ 104 private WnnEngine mConverter; 105 /** Limitation of displaying candidates */ 106 private int mDisplayLimit; 107 108 /** Vibrator for touch vibration */ 109 private Vibrator mVibrator = null; 110 /** MediaPlayer for click sound */ 111 private MediaPlayer mSound = null; 112 113 /** Number of candidates displaying */ 114 private int mWordCount; 115 /** List of candidates */ 116 private ArrayList<WnnWord> mWnnWordArray; 117 118 /** Gesture detector */ 119 private GestureDetector mGestureDetector; 120 /** The word pressed */ 121 private WnnWord mWord; 122 /** Character width of the candidate area */ 123 private int mLineLength = 0; 124 /** Number of lines displayed */ 125 private int mLineCount = 1; 126 127 /** {@code true} if the candidate delete state is selected */ 128 private boolean mIsScaleUp = false; 129 130 /** {@code true} if the full screen mode is selected */ 131 private boolean mIsFullView = false; 132 133 /** The event object for "touch" */ 134 private MotionEvent mMotionEvent = null; 135 136 /** The offset when the candidates is flowed out the candidate window */ 137 private int mDisplayEndOffset = 0; 138 /** {@code true} if there are more candidates to display. */ 139 private boolean mCanReadMore = false; 140 /** Width of {@code mReadMoreButton} */ 141 private int mReadMoreButtonWidth = 0; 142 /** Color of the candidates */ 143 private int mTextColor = 0; 144 /** Template object for each candidate and normal/full view change button */ 145 private TextView mViewCandidateTemplate; 146 /** Number of candidates in full view */ 147 private int mFullViewWordCount; 148 /** Number of candidates in the current line (in full view) */ 149 private int mFullViewOccupyCount; 150 /** View of the previous candidate (in full view) */ 151 private TextView mFullViewPrevView; 152 /** Id of the top line view (in full view) */ 153 private int mFullViewPrevLineTopId; 154 /** Layout of the previous candidate (in full view) */ 155 private RelativeLayout.LayoutParams mFullViewPrevParams; 156 /** Whether all candidates is displayed */ 157 private boolean mCreateCandidateDone; 158 /** Number of lines in normal view */ 159 private int mNormalViewWordCountOfLine; 160 /** general infomation about a display */ 161 private final DisplayMetrics mMetrics = new DisplayMetrics(); 162 163 /** Event listener for touching a candidate */ 164 private OnTouchListener mCandidateOnTouch = new OnTouchListener() { 165 public boolean onTouch(View v, MotionEvent event) { 166 if (mMotionEvent != null) { 167 return true; 168 } 169 170 if ((event.getAction() == MotionEvent.ACTION_UP) 171 && (v instanceof TextView)) { 172 Drawable d = v.getBackground(); 173 if (d != null) { 174 d.setState(new int[] {}); 175 } 176 } 177 178 mMotionEvent = event; 179 boolean ret = mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.CANDIDATE_VIEW_TOUCH)); 180 mMotionEvent = null; 181 return ret; 182 } 183 }; 184 185 186 /** Event listener for clicking a candidate */ 187 private OnClickListener mCandidateOnClick = new OnClickListener() { 188 public void onClick(View v) { 189 if (!v.isShown()) { 190 return; 191 } 192 193 if (v instanceof TextView) { 194 TextView text = (TextView)v; 195 int wordcount = text.getId(); 196 WnnWord word = null; 197 word = mWnnWordArray.get(wordcount); 198 selectCandidate(word); 199 } 200 } 201 }; 202 203 /** Event listener for long-clicking a candidate */ 204 private OnLongClickListener mCandidateOnLongClick = new OnLongClickListener() { 205 public boolean onLongClick(View v) { 206 if (mViewScaleUp == null) { 207 return false; 208 } 209 210 if (!v.isShown()) { 211 return true; 212 } 213 214 Drawable d = v.getBackground(); 215 if (d != null) { 216 if(d.getState().length == 0){ 217 return true; 218 } 219 } 220 221 int wordcount = ((TextView)v).getId(); 222 mWord = mWnnWordArray.get(wordcount); 223 setViewScaleUp(true, mWord); 224 225 return true; 226 } 227 }; 228 229 230 /** 231 * Constructor 232 */ TextCandidatesViewManager()233 public TextCandidatesViewManager() { 234 this(-1); 235 } 236 237 /** 238 * Constructor 239 * 240 * @param displayLimit The limit of display 241 */ TextCandidatesViewManager(int displayLimit)242 public TextCandidatesViewManager(int displayLimit) { 243 this.mDisplayLimit = displayLimit; 244 this.mWnnWordArray = new ArrayList<WnnWord>(); 245 this.mAutoHideMode = true; 246 mMetrics.setToDefaults(); 247 } 248 249 /** 250 * Set auto-hide mode. 251 * @param hide {@code true} if the view will hidden when no candidate exists; 252 * {@code false} if the view is always shown. 253 */ setAutoHide(boolean hide)254 public void setAutoHide(boolean hide) { 255 mAutoHideMode = hide; 256 } 257 258 /** @see CandidatesViewManager */ initView(OpenWnn parent, int width, int height)259 public View initView(OpenWnn parent, int width, int height) { 260 mWnn = parent; 261 mViewWidth = width; 262 mViewHeight = height; 263 mPortrait = 264 (parent.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE); 265 266 Resources r = mWnn.getResources(); 267 268 LayoutInflater inflater = parent.getLayoutInflater(); 269 mViewBody = (ViewGroup)inflater.inflate(R.layout.candidates, null); 270 271 mViewBodyScroll = (ScrollView)mViewBody.findViewById(R.id.candview_scroll); 272 mViewBodyScroll.setOnTouchListener(mCandidateOnTouch); 273 274 mViewCandidateBase = (ViewGroup)mViewBody.findViewById(R.id.candview_base); 275 276 createNormalCandidateView(); 277 mViewCandidateList2nd = (RelativeLayout)mViewBody.findViewById(R.id.candidates_2nd_view); 278 279 mReadMoreButtonWidth = r.getDrawable(R.drawable.cand_up).getMinimumWidth(); 280 281 mTextColor = r.getColor(R.color.candidate_text); 282 283 mReadMoreButton = (ImageView)mViewBody.findViewById(R.id.read_more_text); 284 mReadMoreButton.setOnTouchListener(new View.OnTouchListener() { 285 public boolean onTouch(View v, MotionEvent event) { 286 switch (event.getAction()) { 287 case MotionEvent.ACTION_DOWN: 288 if (mIsFullView) { 289 mReadMoreButton.setImageResource(R.drawable.cand_down_press); 290 } else { 291 mReadMoreButton.setImageResource(R.drawable.cand_up_press); 292 } 293 break; 294 case MotionEvent.ACTION_UP: 295 if (mIsFullView) { 296 mReadMoreButton.setImageResource(R.drawable.cand_down); 297 } else { 298 mReadMoreButton.setImageResource(R.drawable.cand_up); 299 } 300 break; 301 default: 302 break; 303 } 304 return false; 305 } 306 }); 307 mReadMoreButton.setOnClickListener(new View.OnClickListener() { 308 public void onClick(View v) { 309 if (!v.isShown()) { 310 return; 311 } 312 313 if (mIsFullView) { 314 mIsFullView = false; 315 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL)); 316 } else { 317 mIsFullView = true; 318 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL)); 319 } 320 } 321 }); 322 323 setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE); 324 325 mGestureDetector = new GestureDetector(this); 326 327 View scaleUp = (View)inflater.inflate(R.layout.candidate_scale_up, null); 328 mViewScaleUp = scaleUp; 329 330 /* select button */ 331 Button b = (Button)scaleUp.findViewById(R.id.candidate_select); 332 b.setOnClickListener(new View.OnClickListener() { 333 public void onClick(View v) { 334 selectCandidate(mWord); 335 } 336 }); 337 338 /* cancel button */ 339 b = (Button)scaleUp.findViewById(R.id.candidate_cancel); 340 b.setOnClickListener(new View.OnClickListener() { 341 public void onClick(View v) { 342 setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL); 343 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.UPDATE_CANDIDATE)); 344 } 345 }); 346 347 return mViewBody; 348 } 349 350 /** 351 * Create the normal candidate view 352 */ createNormalCandidateView()353 private void createNormalCandidateView() { 354 mViewCandidateList1st = (LinearLayout)mViewBody.findViewById(R.id.candidates_1st_view); 355 mViewCandidateList1st.setOnTouchListener(mCandidateOnTouch); 356 mViewCandidateList1st.setOnClickListener(mCandidateOnClick); 357 358 int line = getMaxLine(); 359 int width = mViewWidth; 360 for (int i = 0; i < line; i++) { 361 LinearLayout lineView = new LinearLayout(mViewBodyScroll.getContext()); 362 lineView.setOrientation(LinearLayout.HORIZONTAL); 363 LinearLayout.LayoutParams layoutParams = 364 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, 365 ViewGroup.LayoutParams.WRAP_CONTENT); 366 lineView.setLayoutParams(layoutParams); 367 for (int j = 0; j < (width / getCandidateMinimumWidth()); j++) { 368 TextView tv = createCandidateView(); 369 lineView.addView(tv); 370 } 371 372 if (i == 0) { 373 TextView tv = createCandidateView(); 374 layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 375 ViewGroup.LayoutParams.WRAP_CONTENT); 376 layoutParams.weight = 0; 377 layoutParams.gravity = Gravity.RIGHT; 378 tv.setLayoutParams(layoutParams); 379 380 lineView.addView(tv); 381 mViewCandidateTemplate = tv; 382 } 383 mViewCandidateList1st.addView(lineView); 384 } 385 } 386 387 /** @see CandidatesViewManager#getCurrentView */ getCurrentView()388 public View getCurrentView() { 389 return mViewBody; 390 } 391 392 /** @see CandidatesViewManager#setViewType */ setViewType(int type)393 public void setViewType(int type) { 394 boolean readMore = setViewLayout(type); 395 396 if (readMore) { 397 displayCandidates(this.mConverter, false, -1); 398 } else { 399 if (type == CandidatesViewManager.VIEW_TYPE_NORMAL) { 400 mIsFullView = false; 401 if (mDisplayEndOffset > 0) { 402 int maxLine = getMaxLine(); 403 displayCandidates(this.mConverter, false, maxLine); 404 } else { 405 setReadMore(); 406 } 407 } else { 408 if (mViewBody.isShown()) { 409 mWnn.setCandidatesViewShown(false); 410 } 411 } 412 } 413 } 414 415 /** 416 * Set the view layout 417 * 418 * @param type View type 419 * @return {@code true} if display is updated; {@code false} if otherwise 420 */ setViewLayout(int type)421 private boolean setViewLayout(int type) { 422 mViewType = type; 423 setViewScaleUp(false, null); 424 425 switch (type) { 426 case CandidatesViewManager.VIEW_TYPE_CLOSE: 427 mViewCandidateBase.setMinimumHeight(-1); 428 return false; 429 430 case CandidatesViewManager.VIEW_TYPE_NORMAL: 431 mViewBodyScroll.scrollTo(0, 0); 432 mViewCandidateList1st.setVisibility(View.VISIBLE); 433 mViewCandidateList2nd.setVisibility(View.GONE); 434 mViewCandidateBase.setMinimumHeight(-1); 435 int line = (mPortrait) ? LINE_NUM_PORTRAIT : LINE_NUM_LANDSCAPE; 436 mViewCandidateList1st.setMinimumHeight(getCandidateMinimumHeight() * line); 437 return false; 438 439 case CandidatesViewManager.VIEW_TYPE_FULL: 440 default: 441 mViewCandidateList2nd.setVisibility(View.VISIBLE); 442 mViewCandidateBase.setMinimumHeight(mViewHeight); 443 return true; 444 } 445 } 446 447 /** @see CandidatesViewManager#getViewType */ getViewType()448 public int getViewType() { 449 return mViewType; 450 } 451 452 /** @see CandidatesViewManager#displayCandidates */ displayCandidates(WnnEngine converter)453 public void displayCandidates(WnnEngine converter) { 454 455 mCanReadMore = false; 456 mDisplayEndOffset = 0; 457 mIsFullView = false; 458 mFullViewWordCount = 0; 459 mFullViewOccupyCount = 0; 460 mFullViewPrevLineTopId = 0; 461 mCreateCandidateDone = false; 462 mNormalViewWordCountOfLine = 0; 463 464 clearCandidates(); 465 mConverter = converter; 466 setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL); 467 468 mViewCandidateTemplate.setVisibility(View.VISIBLE); 469 mViewCandidateTemplate.setBackgroundResource(R.drawable.cand_back); 470 471 displayCandidates(converter, true, getMaxLine()); 472 } 473 474 /** @see CandidatesViewManager#getMaxLine */ getMaxLine()475 private int getMaxLine() { 476 int maxLine = (mPortrait) ? LINE_NUM_PORTRAIT : LINE_NUM_LANDSCAPE; 477 return maxLine; 478 } 479 480 /** 481 * Display the candidates. 482 * 483 * @param converter {@link WnnEngine} which holds candidates. 484 * @param dispFirst Whether it is the first time displaying the candidates 485 * @param maxLine The maximum number of displaying lines 486 */ displayCandidates(WnnEngine converter, boolean dispFirst, int maxLine)487 synchronized private void displayCandidates(WnnEngine converter, boolean dispFirst, int maxLine) { 488 if (converter == null) { 489 return; 490 } 491 492 /* Concatenate the candidates already got and the last one in dispFirst mode */ 493 int displayLimit = mDisplayLimit; 494 495 boolean isHistorySequence = false; 496 boolean isBreak = false; 497 498 /* Get candidates */ 499 WnnWord result = null; 500 while ((displayLimit == -1 || mWordCount < displayLimit)) { 501 result = converter.getNextCandidate(); 502 503 if (result == null) { 504 break; 505 } 506 507 setCandidate(false, result); 508 509 if (dispFirst && (maxLine < mLineCount)) { 510 mCanReadMore = true; 511 isBreak = true; 512 break; 513 } 514 } 515 516 if (!isBreak && !mCreateCandidateDone) { 517 /* align left if necessary */ 518 createNextLine(); 519 mCreateCandidateDone = true; 520 } 521 522 if (mWordCount < 1) { /* no candidates */ 523 if (mAutoHideMode) { 524 mWnn.setCandidatesViewShown(false); 525 return; 526 } else { 527 mCanReadMore = false; 528 mIsFullView = false; 529 setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL); 530 } 531 } 532 533 setReadMore(); 534 535 if (!(mViewBody.isShown())) { 536 mWnn.setCandidatesViewShown(true); 537 } 538 return; 539 } 540 541 /** 542 * Add a candidate into the list. 543 * @param isCategory {@code true}:caption of category, {@code false}:normal word 544 * @param word A candidate word 545 */ setCandidate(boolean isCategory, WnnWord word)546 private void setCandidate(boolean isCategory, WnnWord word) { 547 int textLength = measureText(word.candidate, 0, word.candidate.length()); 548 TextView template = mViewCandidateTemplate; 549 textLength += template.getPaddingLeft() + template.getPaddingRight(); 550 int maxWidth = mViewWidth; 551 552 TextView textView; 553 if (mIsFullView || getMaxLine() < mLineCount) { 554 /* Full view */ 555 int indentWidth = mViewWidth / FULL_VIEW_DIV; 556 int occupyCount = Math.min((textLength + indentWidth) / indentWidth, FULL_VIEW_DIV); 557 if (isCategory) { 558 occupyCount = FULL_VIEW_DIV; 559 } 560 561 if (FULL_VIEW_DIV < (mFullViewOccupyCount + occupyCount)) { 562 if (FULL_VIEW_DIV != mFullViewOccupyCount) { 563 mFullViewPrevParams.width += (FULL_VIEW_DIV - mFullViewOccupyCount) * indentWidth; 564 mViewCandidateList2nd.updateViewLayout(mFullViewPrevView, mFullViewPrevParams); 565 } 566 mFullViewOccupyCount = 0; 567 mFullViewPrevLineTopId = mFullViewPrevView.getId(); 568 mLineCount++; 569 } 570 571 RelativeLayout layout = mViewCandidateList2nd; 572 573 int width = indentWidth * occupyCount; 574 int height = getCandidateMinimumHeight(); 575 RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height); 576 577 if (mFullViewPrevLineTopId == 0) { 578 params.addRule(RelativeLayout.ALIGN_PARENT_TOP); 579 } else { 580 params.addRule(RelativeLayout.BELOW, mFullViewPrevLineTopId); 581 } 582 583 if (mFullViewOccupyCount == 0) { 584 params.addRule(RelativeLayout.ALIGN_PARENT_LEFT); 585 } else { 586 params.addRule(RelativeLayout.RIGHT_OF, (mWordCount - 1)); 587 } 588 589 textView = (TextView) layout.getChildAt(mFullViewWordCount); 590 if (textView == null) { 591 textView = createCandidateView(); 592 textView.setLayoutParams(params); 593 594 mViewCandidateList2nd.addView(textView); 595 } else { 596 mViewCandidateList2nd.updateViewLayout(textView, params); 597 } 598 599 mFullViewOccupyCount += occupyCount; 600 mFullViewWordCount++; 601 mFullViewPrevView = textView; 602 mFullViewPrevParams = params; 603 604 } else { 605 textLength = Math.max(textLength, getCandidateMinimumWidth()); 606 607 /* Normal view */ 608 int nextEnd = mLineLength + textLength; 609 if (mLineCount == 1) { 610 maxWidth -= getCandidateMinimumWidth(); 611 } 612 613 if ((maxWidth < nextEnd) && (mWordCount != 0)) { 614 createNextLine(); 615 if (getMaxLine() < mLineCount) { 616 mLineLength = 0; 617 /* Call this method again to add the candidate in the full view */ 618 setCandidate(isCategory, word); 619 return; 620 } 621 622 mLineLength = textLength; 623 } else { 624 mLineLength = nextEnd; 625 } 626 627 LinearLayout lineView = (LinearLayout) mViewCandidateList1st.getChildAt(mLineCount - 1); 628 textView = (TextView) lineView.getChildAt(mNormalViewWordCountOfLine); 629 630 if (isCategory) { 631 if (mLineCount == 1) { 632 mViewCandidateTemplate.setBackgroundDrawable(null); 633 } 634 mLineLength += CANDIDATE_LEFT_ALIGN_THRESHOLD; 635 } 636 637 mNormalViewWordCountOfLine++; 638 } 639 640 textView.setText(word.candidate); 641 textView.setTextColor(mTextColor); 642 textView.setId(mWordCount); 643 textView.setVisibility(View.VISIBLE); 644 textView.setPressed(false); 645 646 if (isCategory) { 647 textView.setOnClickListener(null); 648 textView.setOnLongClickListener(null); 649 textView.setBackgroundDrawable(null); 650 } else { 651 textView.setOnClickListener(mCandidateOnClick); 652 textView.setOnLongClickListener(mCandidateOnLongClick); 653 textView.setBackgroundResource(R.drawable.cand_back); 654 } 655 textView.setOnTouchListener(mCandidateOnTouch); 656 657 if (maxWidth < textLength) { 658 textView.setEllipsize(TextUtils.TruncateAt.END); 659 } else { 660 textView.setEllipsize(null); 661 } 662 663 ImageSpan span = null; 664 if (word.candidate.equals(" ")) { 665 span = new ImageSpan(mWnn, R.drawable.word_half_space, 666 DynamicDrawableSpan.ALIGN_BASELINE); 667 } else if (word.candidate.equals("\u3000" /* full-width space */)) { 668 span = new ImageSpan(mWnn, R.drawable.word_full_space, 669 DynamicDrawableSpan.ALIGN_BASELINE); 670 } 671 672 if (span != null) { 673 SpannableString spannable = new SpannableString(" "); 674 spannable.setSpan(span, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 675 textView.setText(spannable); 676 } 677 678 mWnnWordArray.add(mWordCount, word); 679 mWordCount++; 680 } 681 682 /** 683 * Create a view for a candidate. 684 * @return the view 685 */ createCandidateView()686 private TextView createCandidateView() { 687 TextView text = new TextView(mViewBodyScroll.getContext()); 688 text.setTextSize(20); 689 text.setBackgroundResource(R.drawable.cand_back); 690 text.setGravity(Gravity.CENTER); 691 text.setSingleLine(); 692 text.setPadding(4, 4, 4, 4); 693 text.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 694 ViewGroup.LayoutParams.WRAP_CONTENT, 695 1.0f)); 696 text.setMinHeight(getCandidateMinimumHeight()); 697 text.setMinimumWidth(getCandidateMinimumWidth()); 698 return text; 699 } 700 701 /** 702 * Display {@code mReadMoreText} if there are more candidates. 703 */ setReadMore()704 private void setReadMore() { 705 if (mIsScaleUp) { 706 mReadMoreButton.setVisibility(View.GONE); 707 mViewCandidateTemplate.setVisibility(View.GONE); 708 return; 709 } 710 711 if (mIsFullView) { 712 mReadMoreButton.setVisibility(View.VISIBLE); 713 mReadMoreButton.setImageResource(R.drawable.cand_down); 714 } else { 715 if (mCanReadMore) { 716 mReadMoreButton.setVisibility(View.VISIBLE); 717 mReadMoreButton.setImageResource(R.drawable.cand_up); 718 } else { 719 mReadMoreButton.setVisibility(View.GONE); 720 mViewCandidateTemplate.setVisibility(View.GONE); 721 } 722 } 723 } 724 725 /** 726 * Clear the list of the normal candidate view. 727 */ clearNormalViewCandidate()728 private void clearNormalViewCandidate() { 729 LinearLayout candidateList = mViewCandidateList1st; 730 int lineNum = candidateList.getChildCount(); 731 for (int i = 0; i < lineNum; i++) { 732 733 LinearLayout lineView = (LinearLayout)candidateList.getChildAt(i); 734 int size = lineView.getChildCount(); 735 for (int j = 0; j < size; j++) { 736 View v = lineView.getChildAt(j); 737 v.setVisibility(View.GONE); 738 } 739 } 740 } 741 742 /** @see CandidatesViewManager#clearCandidates */ clearCandidates()743 public void clearCandidates() { 744 clearNormalViewCandidate(); 745 746 RelativeLayout layout = mViewCandidateList2nd; 747 int size = layout.getChildCount(); 748 for (int i = 0; i < size; i++) { 749 View v = layout.getChildAt(i); 750 v.setVisibility(View.GONE); 751 } 752 753 mLineCount = 1; 754 mWordCount = 0; 755 mWnnWordArray.clear(); 756 757 mLineLength = 0; 758 759 mIsFullView = false; 760 setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL); 761 if (mAutoHideMode) { 762 setViewLayout(CandidatesViewManager.VIEW_TYPE_CLOSE); 763 } 764 765 if (mAutoHideMode && mViewBody.isShown()) { 766 mWnn.setCandidatesViewShown(false); 767 } 768 mCanReadMore = false; 769 setReadMore(); 770 } 771 772 /** @see CandidatesViewManager#setPreferences */ setPreferences(SharedPreferences pref)773 public void setPreferences(SharedPreferences pref) { 774 try { 775 if (pref.getBoolean("key_vibration", false)) { 776 mVibrator = (Vibrator)mWnn.getSystemService(Context.VIBRATOR_SERVICE); 777 } else { 778 mVibrator = null; 779 } 780 if (pref.getBoolean("key_sound", false)) { 781 mSound = MediaPlayer.create(mWnn, R.raw.type); 782 } else { 783 mSound = null; 784 } 785 } catch (Exception ex) { 786 Log.d("iwnn", "NO VIBRATOR"); 787 } 788 } 789 790 /** 791 * Process {@code OpenWnnEvent.CANDIDATE_VIEW_TOUCH} event. 792 * 793 * @return {@code true} if event is processed; {@code false} if otherwise 794 */ onTouchSync()795 public boolean onTouchSync() { 796 return mGestureDetector.onTouchEvent(mMotionEvent); 797 } 798 799 /** 800 * Select a candidate. 801 * <br> 802 * This method notices the selected word to {@link OpenWnn}. 803 * 804 * @param word The selected word 805 */ selectCandidate(WnnWord word)806 private void selectCandidate(WnnWord word) { 807 setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL); 808 if (mVibrator != null) { 809 try { mVibrator.vibrate(30); } catch (Exception ex) { } 810 } 811 if (mSound != null) { 812 try { mSound.seekTo(0); mSound.start(); } catch (Exception ex) { } 813 } 814 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.SELECT_CANDIDATE, word)); 815 } 816 817 /** @see android.view.GestureDetector.OnGestureListener#onDown */ onDown(MotionEvent arg0)818 public boolean onDown(MotionEvent arg0) { 819 return false; 820 } 821 822 /** @see android.view.GestureDetector.OnGestureListener#onFling */ onFling(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3)823 public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) { 824 if (mIsScaleUp) { 825 return false; 826 } 827 828 boolean consumed = false; 829 if (arg1 != null && arg0 != null && arg1.getY() < arg0.getY()) { 830 if ((mViewType == CandidatesViewManager.VIEW_TYPE_NORMAL) && mCanReadMore) { 831 if (mVibrator != null) { 832 try { mVibrator.vibrate(30); } catch (Exception ex) { } 833 } 834 mIsFullView = true; 835 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL)); 836 consumed = true; 837 } 838 } else { 839 if (mViewBodyScroll.getScrollY() == 0) { 840 if (mVibrator != null) { 841 try { mVibrator.vibrate(30); } catch (Exception ex) { } 842 } 843 mIsFullView = false; 844 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL)); 845 consumed = true; 846 } 847 } 848 849 return consumed; 850 } 851 852 /** @see android.view.GestureDetector.OnGestureListener#onLongPress */ onLongPress(MotionEvent arg0)853 public void onLongPress(MotionEvent arg0) { 854 return; 855 } 856 857 /** @see android.view.GestureDetector.OnGestureListener#onScroll */ onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3)858 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) { 859 return false; 860 } 861 862 /** @see android.view.GestureDetector.OnGestureListener#onShowPress */ onShowPress(MotionEvent arg0)863 public void onShowPress(MotionEvent arg0) { 864 } 865 866 /** @see android.view.GestureDetector.OnGestureListener#onSingleTapUp */ onSingleTapUp(MotionEvent arg0)867 public boolean onSingleTapUp(MotionEvent arg0) { 868 return false; 869 } 870 871 /** 872 * Retrieve the width of string to draw. 873 * 874 * @param text The string 875 * @param start The start position (specified by the number of character) 876 * @param end The end position (specified by the number of character) 877 * @return The width of string to draw 878 */ measureText(CharSequence text, int start, int end)879 public int measureText(CharSequence text, int start, int end) { 880 TextPaint paint = mViewCandidateTemplate.getPaint(); 881 return (int)paint.measureText(text, start, end); 882 } 883 884 /** 885 * Switch list/enlarge view mode. 886 * @param up {@code true}:enlarge, {@code false}:list 887 * @param word The candidate word to be enlarged. 888 */ setViewScaleUp(boolean up, WnnWord word)889 private void setViewScaleUp(boolean up, WnnWord word) { 890 if (up == mIsScaleUp || (mViewScaleUp == null)) { 891 return; 892 } 893 894 if (up) { 895 setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL); 896 mViewCandidateList1st.setVisibility(View.GONE); 897 mViewCandidateBase.setMinimumHeight(-1); 898 mViewCandidateBase.addView(mViewScaleUp); 899 TextView text = (TextView)mViewScaleUp.findViewById(R.id.candidate_scale_up_text); 900 text.setText(word.candidate); 901 if (!mPortrait) { 902 Resources r = mViewBodyScroll.getContext().getResources(); 903 text.setTextSize(r.getDimensionPixelSize(R.dimen.candidate_delete_word_size_landscape)); 904 } 905 906 mIsScaleUp = true; 907 setReadMore(); 908 } else { 909 mIsScaleUp = false; 910 mViewCandidateBase.removeView(mViewScaleUp); 911 } 912 } 913 914 /** 915 * Create a layout for the next line. 916 */ createNextLine()917 private void createNextLine() { 918 int lineCount = mLineCount; 919 if (mIsFullView || getMaxLine() < lineCount) { 920 /* Full view */ 921 mFullViewOccupyCount = 0; 922 mFullViewPrevLineTopId = mFullViewPrevView.getId(); 923 } else { 924 /* Normal view */ 925 LinearLayout lineView = (LinearLayout) mViewCandidateList1st.getChildAt(lineCount - 1); 926 float weight = 0; 927 if (mLineLength < CANDIDATE_LEFT_ALIGN_THRESHOLD) { 928 if (lineCount == 1) { 929 mViewCandidateTemplate.setVisibility(View.GONE); 930 } 931 } else { 932 weight = 1.0f; 933 } 934 935 LinearLayout.LayoutParams params 936 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 937 ViewGroup.LayoutParams.WRAP_CONTENT, 938 weight); 939 940 int child = lineView.getChildCount(); 941 for (int i = 0; i < child; i++) { 942 View view = lineView.getChildAt(i); 943 944 if (view != mViewCandidateTemplate) { 945 view.setLayoutParams(params); 946 } 947 } 948 949 mLineLength = 0; 950 mNormalViewWordCountOfLine = 0; 951 } 952 mLineCount++; 953 } 954 955 /** 956 * @return the minimum width of a candidate view. 957 */ getCandidateMinimumWidth()958 private int getCandidateMinimumWidth() { 959 return (int)(CANDIDATE_MINIMUM_WIDTH * mMetrics.density); 960 } 961 962 /** 963 * @return the minimum height of a candidate view. 964 */ getCandidateMinimumHeight()965 private int getCandidateMinimumHeight() { 966 return (int)(CANDIDATE_MINIMUM_HEIGHT * mMetrics.density); 967 } 968 } 969