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 17 package jp.co.omronsoft.openwnn; 18 19 import java.nio.channels.Selector; 20 import java.util.ArrayList; 21 import java.util.LinkedList; 22 import android.content.Context; 23 import android.content.SharedPreferences; 24 import android.content.res.Resources; 25 import android.content.res.Configuration; 26 import android.graphics.drawable.Drawable; 27 import android.graphics.Rect; 28 import android.media.AudioManager; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.os.Vibrator; 32 import android.preference.PreferenceManager; 33 import android.text.TextUtils; 34 import android.text.TextPaint; 35 import android.text.SpannableString; 36 import android.text.Spanned; 37 import android.text.style.ImageSpan; 38 import android.text.style.DynamicDrawableSpan; 39 import android.util.Log; 40 import android.util.DisplayMetrics; 41 import android.util.TypedValue; 42 import android.view.Gravity; 43 import android.view.KeyEvent; 44 import android.view.MotionEvent; 45 import android.view.View; 46 import android.view.ViewGroup; 47 import android.view.View.OnClickListener; 48 import android.view.View.OnLongClickListener; 49 import android.view.GestureDetector; 50 import android.view.LayoutInflater; 51 import android.widget.Button; 52 import android.widget.LinearLayout; 53 import android.widget.ScrollView; 54 import android.widget.TextView; 55 import android.widget.EditText; 56 import android.widget.AbsoluteLayout; 57 import android.widget.ImageView; 58 59 /** 60 * The default candidates view manager class using {@link EditText}. 61 * 62 * @author Copyright (C) 2009-2011 OMRON SOFTWARE CO., LTD. All Rights Reserved. 63 */ 64 public class TextCandidatesViewManager extends CandidatesViewManager implements GestureDetector.OnGestureListener { 65 /** Number of lines to display (Portrait) */ 66 public static final int LINE_NUM_PORTRAIT = 2; 67 /** Number of lines to display (Landscape) */ 68 public static final int LINE_NUM_LANDSCAPE = 1; 69 70 /** Maximum lines */ 71 private static final int DISPLAY_LINE_MAX_COUNT = 1000; 72 /** Maximum number of displaying candidates par one line (full view mode) */ 73 private static final int FULL_VIEW_DIV = 4; 74 /** Maximum number of displaying candidates par one line (full view mode)(symbol)(portrait) */ 75 private static final int FULL_VIEW_SYMBOL_DIV_PORT = 6; 76 /** Maximum number of displaying candidates par one line (full view mode)(symbol)(landscape) */ 77 private static final int FULL_VIEW_SYMBOL_DIV_LAND = 10; 78 /** Delay of set candidate */ 79 private static final int SET_CANDIDATE_DELAY = 50; 80 /** First line count */ 81 private static final int SET_CANDIDATE_FIRST_LINE_COUNT = 7; 82 /** Delay line count */ 83 private static final int SET_CANDIDATE_DELAY_LINE_COUNT = 1; 84 85 /** Focus is none now */ 86 private static final int FOCUS_NONE = -1; 87 /** Handler for focus Candidate */ 88 private static final int MSG_MOVE_FOCUS = 0; 89 /** Handler for set Candidate */ 90 private static final int MSG_SET_CANDIDATES = 1; 91 /** Handler for select Candidate */ 92 private static final int MSG_SELECT_CANDIDATES = 2; 93 94 /** NUmber of Candidate display lines */ 95 private static final int SETTING_NUMBER_OF_LINEMAX = 5; 96 97 /** Body view of the candidates list */ 98 private ViewGroup mViewBody = null; 99 100 /** The view of the Symbol Tab */ 101 private TextView mViewTabSymbol; 102 /** The view of the Emoticon Tab */ 103 private TextView mViewTabEmoticon; 104 /** Scroller of {@code mViewBodyText} */ 105 private ScrollView mViewBodyScroll; 106 /** Base of {@code mViewCandidateList1st}, {@code mViewCandidateList2nd} */ 107 private ViewGroup mViewCandidateBase; 108 /** Button displayed bottom of the view when there are more candidates. */ 109 private ImageView mReadMoreButton; 110 /** Layout for the candidates list on normal view */ 111 private LinearLayout mViewCandidateList1st; 112 /** Layout for the candidates list on full view */ 113 private AbsoluteLayout mViewCandidateList2nd; 114 /** View for symbol tab */ 115 private LinearLayout mViewCandidateListTab; 116 /** {@link OpenWnn} instance using this manager */ 117 private OpenWnn mWnn; 118 /** View type (VIEW_TYPE_NORMAL or VIEW_TYPE_FULL or VIEW_TYPE_CLOSE) */ 119 private int mViewType; 120 /** Portrait display({@code true}) or landscape({@code false}) */ 121 private boolean mPortrait; 122 123 /** Width of the view */ 124 private int mViewWidth; 125 /** Minimum width of a candidate (density support) */ 126 private int mCandidateMinimumWidth; 127 /** Maximum width of a candidate (density support) */ 128 private int mCandidateMinimumHeight; 129 /** Minimum height of the category candidate view */ 130 private int mCandidateCategoryMinimumHeight; 131 /** Left align threshold of the candidate view */ 132 private int mCandidateLeftAlignThreshold; 133 /** Height of keyboard */ 134 private int mKeyboardHeight; 135 /** Height of symbol keyboard */ 136 private int mSymbolKeyboardHeight; 137 /** Height of symbol keyboard tab */ 138 private int mSymbolKeyboardTabHeight; 139 /** Whether being able to use Emoticon */ 140 private boolean mEnableEmoticon = false; 141 142 /** Whether hide the view if there is no candidate */ 143 private boolean mAutoHideMode = true; 144 /** The converter to get candidates from and notice the selected candidate to. */ 145 private WnnEngine mConverter; 146 /** Limitation of displaying candidates */ 147 private int mDisplayLimit; 148 149 /** Vibrator for touch vibration */ 150 private Vibrator mVibrator = null; 151 /** AudioManager for click sound */ 152 private AudioManager mSound = null; 153 154 /** Number of candidates displaying for 1st */ 155 private int mWordCount1st; 156 /** Number of candidates displaying for 2nd */ 157 private int mWordCount2nd; 158 /** List of candidates for 1st */ 159 private ArrayList<WnnWord> mWnnWordArray1st = new ArrayList<WnnWord>(); 160 /** List of candidates for 2nd */ 161 private ArrayList<WnnWord> mWnnWordArray2nd = new ArrayList<WnnWord>(); 162 /** List of select candidates */ 163 private LinkedList<WnnWord> mWnnWordSelectedList = new LinkedList<WnnWord>(); 164 165 /** Gesture detector */ 166 private GestureDetector mGestureDetector; 167 /** Character width of the candidate area */ 168 private int mLineLength = 0; 169 /** Number of lines displayed */ 170 private int mLineCount = 1; 171 172 /** {@code true} if the full screen mode is selected */ 173 private boolean mIsFullView = false; 174 175 /** The event object for "touch" */ 176 private MotionEvent mMotionEvent = null; 177 178 /** The offset when the candidates is flowed out the candidate window */ 179 private int mDisplayEndOffset = 0; 180 /** {@code true} if there are more candidates to display. */ 181 private boolean mCanReadMore = false; 182 /** Color of the candidates */ 183 private int mTextColor = 0; 184 /** Template object for each candidate and normal/full view change button */ 185 private TextView mViewCandidateTemplate; 186 /** Number of candidates in full view */ 187 private int mFullViewWordCount; 188 /** Number of candidates in the current line (in full view) */ 189 private int mFullViewOccupyCount; 190 /** View of the previous candidate (in full view) */ 191 private TextView mFullViewPrevView; 192 /** Id of the top line view (in full view) */ 193 private int mFullViewPrevLineTopId; 194 /** Layout of the previous candidate (in full view) */ 195 private ViewGroup.LayoutParams mFullViewPrevParams; 196 /** Whether all candidates are displayed */ 197 private boolean mCreateCandidateDone; 198 /** Number of lines in normal view */ 199 private int mNormalViewWordCountOfLine; 200 201 /** List of textView for CandiData List 1st for Symbol mode */ 202 private ArrayList<TextView> mTextViewArray1st = new ArrayList<TextView>(); 203 /** List of textView for CandiData List 2st for Symbol mode */ 204 private ArrayList<TextView> mTextViewArray2nd = new ArrayList<TextView>(); 205 /** Now focus textView index */ 206 private int mCurrentFocusIndex = FOCUS_NONE; 207 /** Focused View */ 208 private View mFocusedView = null; 209 /** Focused View Background */ 210 private Drawable mFocusedViewBackground = null; 211 /** Axis to find next TextView for Up/Down */ 212 private int mFocusAxisX = 0; 213 /** Now focused TextView in mTextViewArray1st */ 214 private boolean mHasFocusedArray1st = true; 215 216 /** Portrait Number of Lines from Preference */ 217 private int mPortraitNumberOfLine = LINE_NUM_PORTRAIT; 218 /** Landscape Number of Lines from Preference */ 219 private int mLandscapeNumberOfLine = LINE_NUM_LANDSCAPE; 220 221 /** Coordinates of line */ 222 private int mLineY = 0; 223 224 /** {@code true} if the candidate is selected */ 225 private boolean mIsSymbolSelected = false; 226 227 /** Whether candidates is symbol */ 228 private boolean mIsSymbolMode = false; 229 230 /** Symbol mode */ 231 private int mSymbolMode = OpenWnnJAJP.ENGINE_MODE_SYMBOL; 232 233 /** Text size of candidates */ 234 private float mCandNormalTextSize; 235 236 /** Text size of category */ 237 private float mCandCategoryTextSize; 238 239 /** HardKeyboard hidden({@code true}) or disp({@code false}) */ 240 private boolean mHardKeyboardHidden = true; 241 242 /** Minimum height of the candidate 1line view */ 243 private int mCandidateOneLineMinimumHeight; 244 245 /** Whether candidates long click enable */ 246 private boolean mEnableCandidateLongClick = true; 247 248 /** Keyboard vertical gap value */ 249 private static final float KEYBOARD_VERTICAL_GAP = 0.009f; 250 251 /** Keyboard vertical gap count */ 252 private static final int KEYBOARD_VERTICAL_GAP_COUNT = 3; 253 254 /** {@code Handler} Handler for focus Candidate wait delay */ 255 private Handler mHandler = new Handler() { 256 @Override public void handleMessage(Message msg) { 257 switch (msg.what) { 258 case MSG_MOVE_FOCUS: 259 moveFocus(msg.arg1, msg.arg2 == 1); 260 break; 261 262 case MSG_SET_CANDIDATES: 263 if (mViewType == CandidatesViewManager.VIEW_TYPE_FULL && mIsSymbolMode) { 264 displayCandidates(mConverter, false, SET_CANDIDATE_DELAY_LINE_COUNT); 265 } 266 break; 267 268 case MSG_SELECT_CANDIDATES: 269 WnnWord word = null; 270 while ((word = mWnnWordSelectedList.poll()) != null) { 271 selectCandidate(word); 272 } 273 break; 274 275 default: 276 break; 277 } 278 } 279 }; 280 281 /** Event listener for touching a candidate for 1st */ 282 private OnClickListener mCandidateOnClick1st = new OnClickListener() { 283 public void onClick(View v) { 284 onClickCandidate(v, mWnnWordArray1st); 285 } 286 }; 287 288 /** Event listener for touching a candidate for 2nd */ 289 private OnClickListener mCandidateOnClick2nd = new OnClickListener() { 290 public void onClick(View v) { 291 onClickCandidate(v, mWnnWordArray2nd); 292 } 293 }; 294 295 /** Event listener for long-clicking a candidate for 1st */ 296 private OnLongClickListener mCandidateOnLongClick1st = new OnLongClickListener() { 297 public boolean onLongClick(View v) { 298 return onLongClickCandidate(v, mWnnWordArray1st); 299 } 300 }; 301 302 /** Event listener for long-clicking a candidate for for 2nd */ 303 private OnLongClickListener mCandidateOnLongClick2nd = new OnLongClickListener() { 304 public boolean onLongClick(View v) { 305 return onLongClickCandidate(v, mWnnWordArray2nd); 306 } 307 }; 308 309 /** Event listener for click a symbol tab */ 310 private OnClickListener mTabOnClick = new OnClickListener() { 311 public void onClick(View v) { 312 if (!v.isShown()) { 313 return; 314 } 315 playSoundAndVibration(); 316 317 if (v instanceof TextView) { 318 TextView text = (TextView)v; 319 switch (text.getId()) { 320 case R.id.candview_symbol: 321 if (mSymbolMode != OpenWnnJAJP.ENGINE_MODE_SYMBOL) { 322 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.CHANGE_MODE, 323 OpenWnnJAJP.ENGINE_MODE_SYMBOL)); 324 } 325 break; 326 327 case R.id.candview_emoticon: 328 if (mSymbolMode != OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI) { 329 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.CHANGE_MODE, 330 OpenWnnJAJP.ENGINE_MODE_SYMBOL)); 331 } 332 break; 333 334 default: 335 break; 336 } 337 } 338 } 339 }; 340 341 /** 342 * Constructor 343 */ 344 public TextCandidatesViewManager() { 345 this(-1); 346 } 347 348 /** 349 * Constructor 350 * 351 * @param displayLimit The limit of display 352 */ 353 public TextCandidatesViewManager(int displayLimit) { 354 mDisplayLimit = displayLimit; 355 } 356 357 /** 358 * Handle a click event on the candidate. 359 * @param v View 360 * @param list List of candidates 361 */ 362 private void onClickCandidate(View v, ArrayList<WnnWord> list) { 363 if (!v.isShown()) { 364 return; 365 } 366 playSoundAndVibration(); 367 368 if (v instanceof TextView) { 369 TextView text = (TextView)v; 370 int wordcount = text.getId(); 371 WnnWord word = list.get(wordcount); 372 373 if (mHandler.hasMessages(MSG_SET_CANDIDATES)) { 374 mWnnWordSelectedList.add(word); 375 return; 376 } 377 clearFocusCandidate(); 378 selectCandidate(word); 379 } 380 } 381 382 /** 383 * Handle a long click event on the candidate. 384 * @param v View 385 * @param list List of candidates 386 */ 387 public boolean onLongClickCandidate(View v, ArrayList<WnnWord> list) { 388 if (mViewLongPressDialog == null) { 389 return false; 390 } 391 392 if (mIsSymbolMode) { 393 return false; 394 } 395 396 if (!mEnableCandidateLongClick) { 397 return false; 398 } 399 400 if (!v.isShown()) { 401 return true; 402 } 403 404 Drawable d = v.getBackground(); 405 if (d != null) { 406 if(d.getState().length == 0){ 407 return true; 408 } 409 } 410 411 int wordcount = ((TextView)v).getId(); 412 mWord = list.get(wordcount); 413 clearFocusCandidate(); 414 displayDialog(v, mWord); 415 416 return true; 417 } 418 419 /** 420 * Set auto-hide mode. 421 * @param hide {@code true} if the view will hidden when no candidate exists; 422 * {@code false} if the view is always shown. 423 */ 424 public void setAutoHide(boolean hide) { 425 mAutoHideMode = hide; 426 } 427 428 /** @see CandidatesViewManager#initView */ 429 public View initView(OpenWnn parent, int width, int height) { 430 mWnn = parent; 431 mViewWidth = width; 432 Resources r = mWnn.getResources(); 433 mCandidateMinimumWidth = r.getDimensionPixelSize(R.dimen.cand_minimum_width); 434 mCandidateMinimumHeight = r.getDimensionPixelSize(R.dimen.cand_minimum_height); 435 if (OpenWnn.isXLarge()) { 436 mCandidateOneLineMinimumHeight = r.getDimensionPixelSize(R.dimen.candidate_layout_height); 437 } 438 mCandidateCategoryMinimumHeight = r.getDimensionPixelSize(R.dimen.cand_category_minimum_height); 439 mCandidateLeftAlignThreshold = r.getDimensionPixelSize(R.dimen.cand_left_align_threshold); 440 mKeyboardHeight = r.getDimensionPixelSize(R.dimen.keyboard_height); 441 if (OpenWnn.isXLarge()) { 442 mKeyboardHeight += Math.round(height * KEYBOARD_VERTICAL_GAP) 443 * KEYBOARD_VERTICAL_GAP_COUNT; 444 } 445 mSymbolKeyboardHeight = r.getDimensionPixelSize(R.dimen.symbol_keyboard_height); 446 Drawable d = r.getDrawable(R.drawable.tab_no_select); 447 mSymbolKeyboardTabHeight = d.getMinimumHeight(); 448 449 mPortrait = 450 (r.getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE); 451 452 mCandNormalTextSize = r.getDimensionPixelSize(R.dimen.cand_normal_text_size); 453 mCandCategoryTextSize = r.getDimensionPixelSize(R.dimen.cand_category_text_size); 454 455 LayoutInflater inflater = parent.getLayoutInflater(); 456 mViewBody = (ViewGroup)inflater.inflate(R.layout.candidates, null); 457 458 mViewTabSymbol = (TextView)mViewBody.findViewById(R.id.candview_symbol); 459 mViewTabEmoticon = (TextView)mViewBody.findViewById(R.id.candview_emoticon); 460 461 mViewBodyScroll = (ScrollView)mViewBody.findViewById(R.id.candview_scroll); 462 463 mViewCandidateBase = (ViewGroup)mViewBody.findViewById(R.id.candview_base); 464 465 setNumeberOfDisplayLines(); 466 createNormalCandidateView(); 467 mViewCandidateList2nd = (AbsoluteLayout)mViewBody.findViewById(R.id.candidates_2nd_view); 468 469 mTextColor = r.getColor(R.color.candidate_text); 470 471 mReadMoreButton = (ImageView)mViewBody.findViewById(R.id.read_more_button); 472 mReadMoreButton.setOnTouchListener(new View.OnTouchListener() { 473 public boolean onTouch(View v, MotionEvent event) { 474 int resid = 0; 475 switch (event.getAction()) { 476 case MotionEvent.ACTION_DOWN: 477 if (mIsFullView) { 478 resid = R.drawable.cand_up_press; 479 } else { 480 resid = R.drawable.cand_down_press; 481 } 482 break; 483 case MotionEvent.ACTION_UP: 484 if (mIsFullView) { 485 resid = R.drawable.cand_up; 486 } else { 487 resid = R.drawable.cand_down; 488 } 489 break; 490 default: 491 break; 492 } 493 494 if (resid != 0) { 495 mReadMoreButton.setImageResource(resid); 496 } 497 return false; 498 } 499 }); 500 mReadMoreButton.setOnClickListener(new View.OnClickListener() { 501 public void onClick(View v) { 502 if (!v.isShown()) { 503 return; 504 } 505 playSoundAndVibration(); 506 507 if (mIsFullView) { 508 mIsFullView = false; 509 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL)); 510 } else { 511 mIsFullView = true; 512 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL)); 513 } 514 } 515 }); 516 517 setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE); 518 519 mGestureDetector = new GestureDetector(this); 520 521 mViewLongPressDialog = (View)inflater.inflate(R.layout.candidate_longpress_dialog, null); 522 523 /* select button */ 524 Button longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_select); 525 longPressDialogButton.setOnClickListener(new View.OnClickListener() { 526 public void onClick(View v) { 527 playSoundAndVibration(); 528 clearFocusCandidate(); 529 selectCandidate(mWord); 530 closeDialog(); 531 } 532 }); 533 534 /* cancel button */ 535 longPressDialogButton = (Button)mViewLongPressDialog.findViewById(R.id.candidate_longpress_dialog_cancel); 536 longPressDialogButton.setOnClickListener(new View.OnClickListener() { 537 public void onClick(View v) { 538 playSoundAndVibration(); 539 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL)); 540 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.UPDATE_CANDIDATE)); 541 closeDialog(); 542 } 543 }); 544 545 return mViewBody; 546 } 547 548 /** 549 * Create the normal candidate view 550 */ 551 private void createNormalCandidateView() { 552 mViewCandidateList1st = (LinearLayout)mViewBody.findViewById(R.id.candidates_1st_view); 553 mViewCandidateList1st.setOnClickListener(mCandidateOnClick1st); 554 555 mViewCandidateListTab = (LinearLayout)mViewBody.findViewById(R.id.candview_tab); 556 TextView tSymbol = mViewTabSymbol; 557 tSymbol.setOnClickListener(mTabOnClick); 558 TextView tEmoticon = mViewTabEmoticon; 559 tEmoticon.setOnClickListener(mTabOnClick); 560 561 int line = SETTING_NUMBER_OF_LINEMAX; 562 int width = mViewWidth; 563 for (int i = 0; i < line; i++) { 564 LinearLayout lineView = new LinearLayout(mViewBodyScroll.getContext()); 565 lineView.setOrientation(LinearLayout.HORIZONTAL); 566 LinearLayout.LayoutParams layoutParams = 567 new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 568 ViewGroup.LayoutParams.WRAP_CONTENT); 569 lineView.setLayoutParams(layoutParams); 570 for (int j = 0; j < (width / getCandidateMinimumWidth()); j++) { 571 TextView tv = createCandidateView(); 572 lineView.addView(tv); 573 } 574 575 if (i == 0) { 576 TextView tv = createCandidateView(); 577 layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 578 ViewGroup.LayoutParams.WRAP_CONTENT); 579 layoutParams.weight = 0; 580 layoutParams.gravity = Gravity.RIGHT; 581 tv.setLayoutParams(layoutParams); 582 583 lineView.addView(tv); 584 mViewCandidateTemplate = tv; 585 } 586 mViewCandidateList1st.addView(lineView); 587 } 588 } 589 590 /** @see CandidatesViewManager#getCurrentView */ 591 public View getCurrentView() { 592 return mViewBody; 593 } 594 595 /** @see CandidatesViewManager#setViewType */ 596 public void setViewType(int type) { 597 boolean readMore = setViewLayout(type); 598 599 if (readMore) { 600 displayCandidates(this.mConverter, false, -1); 601 } else { 602 if (type == CandidatesViewManager.VIEW_TYPE_NORMAL) { 603 mIsFullView = false; 604 if (mDisplayEndOffset > 0) { 605 int maxLine = getMaxLine(); 606 displayCandidates(this.mConverter, false, maxLine); 607 } else { 608 setReadMore(); 609 } 610 } else { 611 if (mViewBody.isShown()) { 612 mWnn.setCandidatesViewShown(false); 613 } 614 } 615 } 616 } 617 618 /** 619 * Set the view layout 620 * 621 * @param type View type 622 * @return {@code true} if display is updated; {@code false} if otherwise 623 */ 624 private boolean setViewLayout(int type) { 625 626 ViewGroup.LayoutParams params; 627 int line = (mPortrait) ? mPortraitNumberOfLine : mLandscapeNumberOfLine; 628 629 if ((mViewType == CandidatesViewManager.VIEW_TYPE_FULL) 630 && (type == CandidatesViewManager.VIEW_TYPE_NORMAL)) { 631 clearFocusCandidate(); 632 } 633 634 mViewType = type; 635 636 switch (type) { 637 case CandidatesViewManager.VIEW_TYPE_CLOSE: 638 params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, 639 getCandidateMinimumHeight() * line); 640 mViewBodyScroll.setLayoutParams(params); 641 mViewCandidateListTab.setVisibility(View.GONE); 642 mViewCandidateBase.setMinimumHeight(-1); 643 mHandler.removeMessages(MSG_SET_CANDIDATES); 644 return false; 645 646 case CandidatesViewManager.VIEW_TYPE_NORMAL: 647 params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, 648 getCandidateMinimumHeight() * line); 649 mViewBodyScroll.setLayoutParams(params); 650 mViewBodyScroll.scrollTo(0, 0); 651 mViewCandidateListTab.setVisibility(View.GONE); 652 mViewCandidateList1st.setVisibility(View.VISIBLE); 653 mViewCandidateList2nd.setVisibility(View.GONE); 654 mViewCandidateBase.setMinimumHeight(-1); 655 return false; 656 657 case CandidatesViewManager.VIEW_TYPE_FULL: 658 default: 659 params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, 660 getCandidateViewHeight()); 661 mViewBodyScroll.setLayoutParams(params); 662 if (mIsSymbolMode) { 663 updateSymbolType(); 664 mViewCandidateListTab.setVisibility(View.VISIBLE); 665 } else { 666 mViewCandidateListTab.setVisibility(View.GONE); 667 } 668 mViewCandidateList2nd.setVisibility(View.VISIBLE); 669 mViewCandidateBase.setMinimumHeight(-1); 670 return true; 671 } 672 } 673 674 /** @see CandidatesViewManager#getViewType */ 675 public int getViewType() { 676 return mViewType; 677 } 678 679 /** @see CandidatesViewManager#displayCandidates */ 680 public void displayCandidates(WnnEngine converter) { 681 682 mHandler.removeMessages(MSG_SET_CANDIDATES); 683 684 if (mIsSymbolSelected) { 685 mIsSymbolSelected = false; 686 if (mSymbolMode == OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI) { 687 return; 688 } 689 690 int prevLineCount = mLineCount; 691 int prevWordCount1st = mWordCount1st; 692 clearNormalViewCandidate(); 693 mWordCount1st = 0; 694 mLineCount = 1; 695 mLineLength = 0; 696 mNormalViewWordCountOfLine = 0; 697 mWnnWordArray1st.clear(); 698 mTextViewArray1st.clear(); 699 if (((prevWordCount1st == 0) && (mWordCount1st == 1)) || 700 (prevLineCount < mLineCount)) { 701 mViewBodyScroll.scrollTo(0, mViewBodyScroll.getScrollY() + getCandidateMinimumHeight()); 702 } 703 if (isFocusCandidate() && mHasFocusedArray1st) { 704 mCurrentFocusIndex = 0; 705 Message m = mHandler.obtainMessage(MSG_MOVE_FOCUS, 0, 0); 706 mHandler.sendMessage(m); 707 } 708 return; 709 } 710 711 mCanReadMore = false; 712 mDisplayEndOffset = 0; 713 mIsFullView = false; 714 mFullViewWordCount = 0; 715 mFullViewOccupyCount = 0; 716 mFullViewPrevLineTopId = 0; 717 mFullViewPrevView = null; 718 mCreateCandidateDone = false; 719 mNormalViewWordCountOfLine = 0; 720 721 clearCandidates(); 722 mConverter = converter; 723 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL)); 724 725 mViewCandidateTemplate.setVisibility(View.VISIBLE); 726 mViewCandidateTemplate.setBackgroundResource(R.drawable.cand_back); 727 728 displayCandidates(converter, true, getMaxLine()); 729 730 if (mIsSymbolMode) { 731 mIsFullView = true; 732 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL)); 733 } 734 } 735 736 /** @see CandidatesViewManager#getMaxLine */ 737 private int getMaxLine() { 738 int maxLine = (mPortrait) ? mPortraitNumberOfLine : mLandscapeNumberOfLine; 739 return maxLine; 740 } 741 742 /** 743 * Get categories text. 744 * @param String Source string replacement 745 * @return String Categories text 746 */ 747 private String getCategoriesText(String categoriesString) { 748 String ret = null; 749 750 Resources r = mWnn.getResources(); 751 if(categoriesString.equals(r.getString(R.string.half_symbol_categories_txt))) { 752 ret = r.getString(R.string.half_symbol_txt); 753 } else if (categoriesString.equals(r.getString(R.string.full_symbol_categories_txt))) { 754 ret = r.getString(R.string.full_symbol_txt); 755 } else { 756 ret = new String(""); 757 } 758 759 return ret; 760 } 761 762 /** 763 * Display the candidates. 764 * 765 * @param converter {@link WnnEngine} which holds candidates. 766 * @param dispFirst Whether it is the first time displaying the candidates 767 * @param maxLine The maximum number of displaying lines 768 */ 769 private void displayCandidates(WnnEngine converter, boolean dispFirst, int maxLine) { 770 if (converter == null) { 771 return; 772 } 773 774 /* Concatenate the candidates already got and the last one in dispFirst mode */ 775 int displayLimit = mDisplayLimit; 776 777 boolean isDelay = false; 778 boolean isBreak = false; 779 780 if (converter instanceof SymbolList) { 781 if (!dispFirst) { 782 if (maxLine == -1) { 783 isDelay = true; 784 maxLine = mLineCount + SET_CANDIDATE_FIRST_LINE_COUNT; 785 786 mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, SET_CANDIDATE_DELAY); 787 788 } else if (maxLine == SET_CANDIDATE_DELAY_LINE_COUNT) { 789 isDelay = true; 790 maxLine = mLineCount + SET_CANDIDATE_DELAY_LINE_COUNT; 791 792 mHandler.sendEmptyMessageDelayed(MSG_SET_CANDIDATES, SET_CANDIDATE_DELAY); 793 } 794 } 795 if (mSymbolMode != OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI) { 796 displayLimit = -1; 797 } 798 } 799 800 /* Get candidates */ 801 WnnWord result = null; 802 String prevCandidate = null; 803 while ((displayLimit == -1 || getWordCount() < displayLimit)) { 804 for (int i = 0; i < DISPLAY_LINE_MAX_COUNT; i++) { 805 result = converter.getNextCandidate(); 806 if (result == null) { 807 break; 808 } 809 810 if (converter instanceof SymbolList) { 811 break; 812 } 813 814 if ((prevCandidate == null) || !prevCandidate.equals(result.candidate)) { 815 break; 816 } 817 } 818 819 if (result == null) { 820 break; 821 } else { 822 prevCandidate = result.candidate; 823 } 824 825 if (converter instanceof SymbolList) { 826 if (isCategory(result)) { 827 if (getWordCount() != 0) { 828 createNextLine(false); 829 } 830 result.candidate = getCategoriesText(result.candidate); 831 setCandidate(true, result); 832 createNextLine(true); 833 continue; 834 } 835 } 836 837 setCandidate(false, result); 838 839 if ((dispFirst || isDelay) && (maxLine < mLineCount)) { 840 mCanReadMore = true; 841 isBreak = true; 842 break; 843 } 844 } 845 846 if (!isBreak && !mCreateCandidateDone) { 847 /* align left if necessary */ 848 createNextLine(false); 849 mCreateCandidateDone = true; 850 mHandler.removeMessages(MSG_SET_CANDIDATES); 851 mHandler.sendMessage(mHandler.obtainMessage(MSG_SELECT_CANDIDATES)); 852 } 853 854 if (getWordCount() < 1) { /* no candidates */ 855 if (mAutoHideMode) { 856 mWnn.setCandidatesViewShown(false); 857 return; 858 } else { 859 mCanReadMore = false; 860 mIsFullView = false; 861 setViewLayout(CandidatesViewManager.VIEW_TYPE_NORMAL); 862 } 863 } 864 865 setReadMore(); 866 867 if (!(mViewBody.isShown())) { 868 mWnn.setCandidatesViewShown(true); 869 } 870 return; 871 } 872 873 /** 874 * Add a candidate into the list. 875 * @param isCategory {@code true}:caption of category, {@code false}:normal word 876 * @param word A candidate word 877 */ 878 private void setCandidate(boolean isCategory, WnnWord word) { 879 int textLength = measureText(word.candidate, 0, word.candidate.length()); 880 TextView template = mViewCandidateTemplate; 881 textLength += template.getPaddingLeft() + template.getPaddingRight(); 882 int maxWidth = mViewWidth; 883 boolean isEmojiSymbol = false; 884 if (mIsSymbolMode && (word.candidate.length() < 3)) { 885 isEmojiSymbol = true; 886 } 887 TextView textView; 888 889 boolean is2nd = isFirstListOver(mIsFullView, mLineCount, word); 890 if (is2nd) { 891 /* Full view */ 892 int viewDivison = getCandidateViewDivison(); 893 int indentWidth = mViewWidth / viewDivison; 894 int occupyCount = Math.min((textLength + indentWidth + getCandidateSpaceWidth(isEmojiSymbol)) / indentWidth, viewDivison); 895 if (isCategory) { 896 occupyCount = viewDivison; 897 } 898 899 if (viewDivison < (mFullViewOccupyCount + occupyCount)) { 900 if (viewDivison != mFullViewOccupyCount) { 901 mFullViewPrevParams.width += (viewDivison - mFullViewOccupyCount) * indentWidth; 902 if (mFullViewPrevView != null) { 903 mViewCandidateList2nd.updateViewLayout(mFullViewPrevView, mFullViewPrevParams); 904 } 905 } 906 mFullViewOccupyCount = 0; 907 if (mFullViewPrevView != null) { 908 mFullViewPrevLineTopId = mFullViewPrevView.getId(); 909 } 910 mLineCount++; 911 if (isCategory) { 912 mLineY += mCandidateCategoryMinimumHeight; 913 } else { 914 mLineY += getCandidateMinimumHeight(); 915 } 916 } 917 if (mFullViewWordCount == 0) { 918 mLineY = 0; 919 } 920 921 ViewGroup layout = mViewCandidateList2nd; 922 923 int width = indentWidth * occupyCount; 924 int height = getCandidateMinimumHeight(); 925 if (isCategory) { 926 height = mCandidateCategoryMinimumHeight; 927 } 928 929 ViewGroup.LayoutParams params = buildLayoutParams(mViewCandidateList2nd, width, height); 930 931 textView = (TextView) layout.getChildAt(mFullViewWordCount); 932 if (textView == null) { 933 textView = createCandidateView(); 934 textView.setLayoutParams(params); 935 936 mViewCandidateList2nd.addView(textView); 937 } else { 938 mViewCandidateList2nd.updateViewLayout(textView, params); 939 } 940 941 mFullViewOccupyCount += occupyCount; 942 mFullViewWordCount++; 943 mFullViewPrevView = textView; 944 mFullViewPrevParams = params; 945 946 } else { 947 int viewDivison = getCandidateViewDivison(); 948 int indentWidth = mViewWidth / viewDivison; 949 textLength = Math.max(textLength, indentWidth); 950 951 /* Normal view */ 952 int nextEnd = mLineLength + textLength; 953 nextEnd += getCandidateSpaceWidth(isEmojiSymbol); 954 955 if (mLineCount == 1 && !mIsSymbolMode) { 956 maxWidth -= getCandidateMinimumWidth(); 957 } 958 959 if ((maxWidth < nextEnd) && (getWordCount() != 0)) { 960 961 createNextLineFor1st(); 962 if (getMaxLine() < mLineCount) { 963 mLineLength = 0; 964 /* Call this method again to add the candidate in the full view */ 965 if (!mIsSymbolSelected) { 966 setCandidate(isCategory, word); 967 } 968 return; 969 } 970 971 mLineLength = textLength; 972 mLineLength += getCandidateSpaceWidth(isEmojiSymbol); 973 } else { 974 mLineLength = nextEnd; 975 } 976 977 LinearLayout lineView = (LinearLayout) mViewCandidateList1st.getChildAt(mLineCount - 1); 978 textView = (TextView) lineView.getChildAt(mNormalViewWordCountOfLine); 979 980 if (isCategory) { 981 if (mLineCount == 1) { 982 mViewCandidateTemplate.setBackgroundDrawable(null); 983 } 984 mLineLength += mCandidateLeftAlignThreshold; 985 } else { 986 int CompareLength = textLength; 987 CompareLength += getCandidateSpaceWidth(isEmojiSymbol); 988 } 989 990 mNormalViewWordCountOfLine++; 991 } 992 993 textView.setText(word.candidate); 994 if (is2nd) { 995 textView.setId(mWordCount2nd); 996 } else { 997 textView.setId(mWordCount1st); 998 } 999 textView.setVisibility(View.VISIBLE); 1000 textView.setPressed(false); 1001 textView.setFocusable(false); 1002 1003 if (isCategory) { 1004 textView.setText(" " + word.candidate); 1005 1006 textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCandCategoryTextSize); 1007 textView.setBackgroundDrawable(null); 1008 textView.setGravity(Gravity.CENTER_VERTICAL); 1009 textView.setMinHeight(mCandidateCategoryMinimumHeight); 1010 textView.setHeight(mCandidateCategoryMinimumHeight); 1011 1012 textView.setOnClickListener(null); 1013 textView.setOnLongClickListener(null); 1014 textView.setTextColor(mTextColor); 1015 } else { 1016 textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCandNormalTextSize); 1017 textView.setGravity(Gravity.CENTER); 1018 textView.setMinHeight(getCandidateMinimumHeight()); 1019 textView.setHeight(getCandidateMinimumHeight()); 1020 1021 if (is2nd) { 1022 textView.setOnClickListener(mCandidateOnClick2nd); 1023 textView.setOnLongClickListener(mCandidateOnLongClick2nd); 1024 } else { 1025 textView.setOnClickListener(mCandidateOnClick1st); 1026 textView.setOnLongClickListener(mCandidateOnLongClick1st); 1027 } 1028 1029 textView.setBackgroundResource(R.drawable.cand_back); 1030 1031 textView.setTextColor(mTextColor); 1032 } 1033 1034 if (maxWidth < textLength) { 1035 textView.setEllipsize(TextUtils.TruncateAt.END); 1036 } else { 1037 textView.setEllipsize(null); 1038 } 1039 1040 ImageSpan span = null; 1041 if (word.candidate.equals(" ")) { 1042 span = new ImageSpan(mWnn, R.drawable.word_half_space, 1043 DynamicDrawableSpan.ALIGN_BASELINE); 1044 } else if (word.candidate.equals("\u3000" /* full-width space */)) { 1045 span = new ImageSpan(mWnn, R.drawable.word_full_space, 1046 DynamicDrawableSpan.ALIGN_BASELINE); 1047 } 1048 1049 if (span != null) { 1050 SpannableString spannable = new SpannableString(" "); 1051 spannable.setSpan(span, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 1052 textView.setText(spannable); 1053 } 1054 textView.setPadding(0, 0, 0, 0); 1055 1056 if (is2nd) { 1057 mWnnWordArray2nd.add(mWordCount2nd, word); 1058 mWordCount2nd++; 1059 mTextViewArray2nd.add(textView); 1060 } else { 1061 mWnnWordArray1st.add(mWordCount1st, word); 1062 mWordCount1st++; 1063 mTextViewArray1st.add(textView); 1064 } 1065 } 1066 1067 /** 1068 * Create AbsoluteLayout.LayoutParams 1069 * @param layout AbsoluteLayout 1070 * @param width The width of the display 1071 * @param height The height of the display 1072 * @return Layout parameter 1073 */ 1074 private ViewGroup.LayoutParams buildLayoutParams(AbsoluteLayout layout, int width, int height) { 1075 1076 int viewDivison = getCandidateViewDivison(); 1077 int indentWidth = mViewWidth / viewDivison; 1078 int x = indentWidth * mFullViewOccupyCount; 1079 int y = mLineY; 1080 ViewGroup.LayoutParams params 1081 = new AbsoluteLayout.LayoutParams(width, height, x, y); 1082 1083 return params; 1084 } 1085 1086 /** 1087 * Create a view for a candidate. 1088 * @return the view 1089 */ 1090 private TextView createCandidateView() { 1091 TextView text = new CandidateTextView(mViewBodyScroll.getContext()); 1092 text.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCandNormalTextSize); 1093 text.setBackgroundResource(R.drawable.cand_back); 1094 text.setCompoundDrawablePadding(0); 1095 text.setGravity(Gravity.CENTER); 1096 text.setSingleLine(); 1097 text.setPadding(0, 0, 0, 0); 1098 text.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 1099 ViewGroup.LayoutParams.WRAP_CONTENT, 1100 1.0f)); 1101 text.setMinHeight(getCandidateMinimumHeight()); 1102 text.setMinimumWidth(getCandidateMinimumWidth()); 1103 text.setSoundEffectsEnabled(false); 1104 return text; 1105 } 1106 1107 /** 1108 * Display {@code mReadMoreText} if there are more candidates. 1109 */ 1110 private void setReadMore() { 1111 if (mIsSymbolMode) { 1112 mReadMoreButton.setVisibility(View.GONE); 1113 mViewCandidateTemplate.setVisibility(View.GONE); 1114 return; 1115 } 1116 1117 int resid = 0; 1118 1119 if (mIsFullView) { 1120 mReadMoreButton.setVisibility(View.VISIBLE); 1121 resid = R.drawable.cand_up; 1122 } else { 1123 if (mCanReadMore) { 1124 mReadMoreButton.setVisibility(View.VISIBLE); 1125 resid = R.drawable.cand_down; 1126 } else { 1127 mReadMoreButton.setVisibility(View.GONE); 1128 mViewCandidateTemplate.setVisibility(View.GONE); 1129 } 1130 } 1131 1132 if (resid != 0) { 1133 mReadMoreButton.setImageResource(resid); 1134 } 1135 } 1136 1137 /** 1138 * Clear the list of the normal candidate view. 1139 */ 1140 private void clearNormalViewCandidate() { 1141 LinearLayout candidateList = mViewCandidateList1st; 1142 int lineNum = candidateList.getChildCount(); 1143 for (int i = 0; i < lineNum; i++) { 1144 1145 LinearLayout lineView = (LinearLayout)candidateList.getChildAt(i); 1146 int size = lineView.getChildCount(); 1147 for (int j = 0; j < size; j++) { 1148 View v = lineView.getChildAt(j); 1149 v.setVisibility(View.GONE); 1150 } 1151 } 1152 } 1153 1154 /** @see CandidatesViewManager#clearCandidates */ 1155 public void clearCandidates() { 1156 closeDialog(); 1157 clearFocusCandidate(); 1158 clearNormalViewCandidate(); 1159 1160 ViewGroup layout = mViewCandidateList2nd; 1161 int size = layout.getChildCount(); 1162 for (int i = 0; i < size; i++) { 1163 View v = layout.getChildAt(i); 1164 v.setVisibility(View.GONE); 1165 } 1166 1167 mLineCount = 1; 1168 mWordCount1st = 0; 1169 mWordCount2nd = 0; 1170 mWnnWordArray1st.clear(); 1171 mWnnWordArray2nd.clear(); 1172 mTextViewArray1st.clear(); 1173 mTextViewArray2nd.clear(); 1174 mLineLength = 0; 1175 1176 mLineY = 0; 1177 1178 mIsFullView = false; 1179 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL)); 1180 if (mAutoHideMode) { 1181 setViewLayout(CandidatesViewManager.VIEW_TYPE_CLOSE); 1182 } 1183 1184 if (mAutoHideMode && mViewBody.isShown()) { 1185 mWnn.setCandidatesViewShown(false); 1186 } 1187 mCanReadMore = false; 1188 setReadMore(); 1189 } 1190 1191 /** @see CandidatesViewManager#setPreferences */ 1192 public void setPreferences(SharedPreferences pref) { 1193 try { 1194 if (pref.getBoolean("key_vibration", false)) { 1195 mVibrator = (Vibrator)mWnn.getSystemService(Context.VIBRATOR_SERVICE); 1196 } else { 1197 mVibrator = null; 1198 } 1199 if (pref.getBoolean("key_sound", false)) { 1200 mSound = (AudioManager)mWnn.getSystemService(Context.AUDIO_SERVICE); 1201 } else { 1202 mSound = null; 1203 } 1204 setNumeberOfDisplayLines(); 1205 } catch (Exception ex) { 1206 Log.e("OpenWnn", "NO VIBRATOR"); 1207 } 1208 } 1209 1210 /** 1211 * Set normal mode. 1212 */ 1213 public void setNormalMode() { 1214 setReadMore(); 1215 mIsFullView = false; 1216 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL)); 1217 } 1218 1219 /** 1220 * Set full mode. 1221 */ 1222 public void setFullMode() { 1223 mIsFullView = true; 1224 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL)); 1225 } 1226 1227 /** 1228 * Set symbol mode. 1229 */ 1230 public void setSymbolMode(boolean enable, int mode) { 1231 if (mIsSymbolMode && !enable) { 1232 setViewType(CandidatesViewManager.VIEW_TYPE_CLOSE); 1233 } 1234 mSymbolMode = mode; 1235 mIsSymbolMode = enable; 1236 } 1237 1238 /** 1239 * Set scroll up. 1240 */ 1241 public void setScrollUp() { 1242 if (!mViewBodyScroll.pageScroll(ScrollView.FOCUS_UP)) { 1243 mViewBodyScroll.scrollTo(0, mViewBodyScroll.getChildAt(0).getHeight()); 1244 } 1245 } 1246 1247 /** 1248 * Set scroll down. 1249 */ 1250 public void setScrollDown() { 1251 if (!mViewBodyScroll.pageScroll(ScrollView.FOCUS_DOWN)) { 1252 mViewBodyScroll.scrollTo(0, 0); 1253 } 1254 } 1255 1256 /** 1257 * Set scroll full up. 1258 */ 1259 public void setScrollFullUp() { 1260 if (!mViewBodyScroll.fullScroll(ScrollView.FOCUS_UP)) { 1261 mViewBodyScroll.scrollTo(0, mViewBodyScroll.getChildAt(0).getHeight()); 1262 } 1263 } 1264 1265 /** 1266 * Set scroll full down. 1267 */ 1268 public void setScrollFullDown() { 1269 if (!mViewBodyScroll.fullScroll(ScrollView.FOCUS_DOWN)) { 1270 mViewBodyScroll.scrollTo(0, 0); 1271 } 1272 } 1273 1274 /** 1275 * Process {@link OpenWnnEvent#CANDIDATE_VIEW_TOUCH} event. 1276 * 1277 * @return {@code true} if event is processed; {@code false} if otherwise 1278 */ 1279 public boolean onTouchSync() { 1280 return mGestureDetector.onTouchEvent(mMotionEvent); 1281 } 1282 1283 /** 1284 * Select a candidate. 1285 * <br> 1286 * This method notices the selected word to {@link OpenWnn}. 1287 * 1288 * @param word The selected word 1289 */ 1290 private void selectCandidate(WnnWord word) { 1291 if (!mIsSymbolMode) { 1292 mIsFullView = false; 1293 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL)); 1294 } 1295 mIsSymbolSelected = mIsSymbolMode; 1296 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.SELECT_CANDIDATE, word)); 1297 } 1298 1299 private void playSoundAndVibration() { 1300 if (mVibrator != null) { 1301 try { 1302 mVibrator.vibrate(5); 1303 } catch (Exception ex) { 1304 Log.e("OpenWnn", "TextCandidatesViewManager::selectCandidate Vibrator " + ex.toString()); 1305 } 1306 } 1307 if (mSound != null) { 1308 try { 1309 mSound.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, -1); 1310 } catch (Exception ex) { 1311 Log.e("OpenWnn", "TextCandidatesViewManager::selectCandidate Sound " + ex.toString()); 1312 } 1313 } 1314 } 1315 1316 /** @see android.view.GestureDetector.OnGestureListener#onDown */ 1317 public boolean onDown(MotionEvent arg0) { 1318 return false; 1319 } 1320 1321 /** @see android.view.GestureDetector.OnGestureListener#onFling */ 1322 public boolean onFling(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) { 1323 boolean consumed = false; 1324 if (arg1 != null && arg0 != null && arg1.getY() < arg0.getY()) { 1325 if ((mViewType == CandidatesViewManager.VIEW_TYPE_NORMAL) && mCanReadMore) { 1326 if (mVibrator != null) { 1327 try { 1328 mVibrator.vibrate(5); 1329 } catch (Exception ex) { 1330 Log.e("iwnn", "TextCandidatesViewManager::onFling Vibrator " + ex.toString()); 1331 } 1332 } 1333 mIsFullView = true; 1334 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_FULL)); 1335 consumed = true; 1336 } 1337 } else { 1338 if (mViewBodyScroll.getScrollY() == 0) { 1339 if (mVibrator != null) { 1340 try { 1341 mVibrator.vibrate(5); 1342 } catch (Exception ex) { 1343 Log.e("iwnn", "TextCandidatesViewManager::onFling Sound " + ex.toString()); 1344 } 1345 } 1346 mIsFullView = false; 1347 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.LIST_CANDIDATES_NORMAL)); 1348 consumed = true; 1349 } 1350 } 1351 1352 return consumed; 1353 } 1354 1355 /** @see android.view.GestureDetector.OnGestureListener#onLongPress */ 1356 public void onLongPress(MotionEvent arg0) { 1357 return; 1358 } 1359 1360 /** @see android.view.GestureDetector.OnGestureListener#onScroll */ 1361 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) { 1362 return false; 1363 } 1364 1365 /** @see android.view.GestureDetector.OnGestureListener#onShowPress */ 1366 public void onShowPress(MotionEvent arg0) { 1367 } 1368 1369 /** @see android.view.GestureDetector.OnGestureListener#onSingleTapUp */ 1370 public boolean onSingleTapUp(MotionEvent arg0) { 1371 return false; 1372 } 1373 1374 /** 1375 * Retrieve the width of string to draw. 1376 * 1377 * @param text The string 1378 * @param start The start position (specified by the number of character) 1379 * @param end The end position (specified by the number of character) 1380 * @return The width of string to draw 1381 */ 1382 public int measureText(CharSequence text, int start, int end) { 1383 if (end - start < 3) { 1384 return getCandidateMinimumWidth(); 1385 } 1386 1387 TextPaint paint = mViewCandidateTemplate.getPaint(); 1388 return (int)paint.measureText(text, start, end); 1389 } 1390 1391 /** 1392 * Create a layout for the next line. 1393 */ 1394 private void createNextLine(boolean isCategory) { 1395 if (isFirstListOver(mIsFullView, mLineCount, null)) { 1396 /* Full view */ 1397 mFullViewOccupyCount = 0; 1398 if (mFullViewPrevView != null) { 1399 mFullViewPrevLineTopId = mFullViewPrevView.getId(); 1400 } 1401 if (isCategory) { 1402 mLineY += mCandidateCategoryMinimumHeight; 1403 } else { 1404 mLineY += getCandidateMinimumHeight(); 1405 } 1406 mLineCount++; 1407 } else { 1408 createNextLineFor1st(); 1409 } 1410 } 1411 1412 /** 1413 * Create a layout for the next line. 1414 */ 1415 private void createNextLineFor1st() { 1416 LinearLayout lineView = (LinearLayout) mViewCandidateList1st.getChildAt(mLineCount - 1); 1417 float weight = 0; 1418 if (mLineLength < mCandidateLeftAlignThreshold) { 1419 if (mLineCount == 1) { 1420 mViewCandidateTemplate.setVisibility(View.GONE); 1421 } 1422 } else { 1423 weight = 1.0f; 1424 } 1425 1426 LinearLayout.LayoutParams params 1427 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 1428 ViewGroup.LayoutParams.WRAP_CONTENT, 1429 weight); 1430 1431 int child = lineView.getChildCount(); 1432 for (int i = 0; i < child; i++) { 1433 View view = lineView.getChildAt(i); 1434 1435 if (view != mViewCandidateTemplate) { 1436 view.setLayoutParams(params); 1437 view.setPadding(0, 0, 0, 0); 1438 } 1439 } 1440 1441 mLineLength = 0; 1442 mNormalViewWordCountOfLine = 0; 1443 mLineCount++; 1444 } 1445 1446 /** 1447 * Judge if it's a category. 1448 * 1449 * @return {@code true} if category 1450 */ 1451 boolean isCategory(WnnWord word) { 1452 int length = word.candidate.length(); 1453 return ((length > 3) && (word.candidate.charAt(0) == '[')); 1454 } 1455 1456 /** 1457 * Get a minimum width of a candidate view. 1458 * 1459 * @return the minimum width of a candidate view. 1460 */ 1461 private int getCandidateMinimumWidth() { 1462 return mCandidateMinimumWidth; 1463 } 1464 1465 /** 1466 * @return the minimum height of a candidate view. 1467 */ 1468 private int getCandidateMinimumHeight() { 1469 return mCandidateMinimumHeight; 1470 } 1471 1472 /** 1473 * Get a height of a candidate view. 1474 * 1475 * @return the height of a candidate view. 1476 */ 1477 private int getCandidateViewHeight() { 1478 if (OpenWnn.isXLarge()) { 1479 return mKeyboardHeight + mCandidateOneLineMinimumHeight - mSymbolKeyboardHeight 1480 - mSymbolKeyboardTabHeight; 1481 } else { 1482 int numberOfLine = (mPortrait) ? mPortraitNumberOfLine : mLandscapeNumberOfLine; 1483 Resources resource = mWnn.getResources(); 1484 Drawable keyboardBackground = resource.getDrawable(R.drawable.keyboard_background); 1485 Rect keyboardPadding = new Rect(0 ,0 ,0 ,0); 1486 keyboardBackground.getPadding(keyboardPadding); 1487 int keyboardTotalPadding = keyboardPadding.top + keyboardPadding.bottom; 1488 if (mIsSymbolMode) { 1489 return mKeyboardHeight + numberOfLine * getCandidateMinimumHeight() 1490 - mSymbolKeyboardHeight - mSymbolKeyboardTabHeight; 1491 } else if (!mHardKeyboardHidden) { 1492 return mKeyboardHeight + numberOfLine * getCandidateMinimumHeight() 1493 - mSymbolKeyboardHeight; 1494 } else { 1495 return mKeyboardHeight + keyboardTotalPadding 1496 + numberOfLine * getCandidateMinimumHeight(); 1497 } 1498 } 1499 } 1500 1501 /** 1502 * Update symbol type. 1503 */ 1504 private void updateSymbolType() { 1505 switch (mSymbolMode) { 1506 case OpenWnnJAJP.ENGINE_MODE_SYMBOL: 1507 updateTabStatus(mViewTabSymbol, true, true); 1508 updateTabStatus(mViewTabEmoticon, mEnableEmoticon, false); 1509 break; 1510 1511 case OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI: 1512 updateTabStatus(mViewTabSymbol, true, false); 1513 updateTabStatus(mViewTabEmoticon, mEnableEmoticon, true); 1514 break; 1515 1516 default: 1517 updateTabStatus(mViewTabSymbol, true, false); 1518 updateTabStatus(mViewTabEmoticon, mEnableEmoticon, false); 1519 break; 1520 } 1521 } 1522 1523 /** 1524 * Update tab status. 1525 * 1526 * @param tab The tab view. 1527 * @param enabled The tab is enabled. 1528 * @param selected The tab is selected. 1529 */ 1530 private void updateTabStatus(TextView tab, boolean enabled, boolean selected) { 1531 tab.setVisibility(View.VISIBLE); 1532 tab.setEnabled(enabled); 1533 int backgroundId = 0; 1534 int colorId = 0; 1535 if (enabled) { 1536 if (selected) { 1537 backgroundId = R.drawable.cand_tab; 1538 colorId = R.color.tab_textcolor_select; 1539 } else { 1540 backgroundId = R.drawable.cand_tab_noselect; 1541 colorId = R.color.tab_textcolor_no_select; 1542 } 1543 } else { 1544 backgroundId = R.drawable.cand_tab_noselect; 1545 colorId = R.color.tab_textcolor_disable; 1546 } 1547 tab.setBackgroundResource(backgroundId); 1548 tab.setTextColor(mWnn.getResources().getColor(colorId)); 1549 } 1550 1551 /** 1552 * Get candidate number of division. 1553 * @return Number of division 1554 */ 1555 private int getCandidateViewDivison() { 1556 int viewDivison; 1557 1558 if (mIsSymbolMode) { 1559 int mode = mSymbolMode; 1560 switch (mode) { 1561 case OpenWnnJAJP.ENGINE_MODE_SYMBOL: 1562 viewDivison = (mPortrait) ? FULL_VIEW_SYMBOL_DIV_PORT : FULL_VIEW_SYMBOL_DIV_LAND; 1563 break; 1564 case OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI: 1565 default: 1566 viewDivison = FULL_VIEW_DIV; 1567 break; 1568 } 1569 } else { 1570 viewDivison = FULL_VIEW_DIV; 1571 } 1572 return viewDivison; 1573 } 1574 1575 /** 1576 * @return Word count 1577 */ 1578 private int getWordCount() { 1579 return mWordCount1st + mWordCount2nd; 1580 } 1581 1582 /** 1583 * @return Add second 1584 */ 1585 private boolean isFirstListOver(boolean isFullView, int lineCount, WnnWord word) { 1586 1587 if (mIsSymbolMode) { 1588 switch (mSymbolMode) { 1589 case OpenWnnJAJP.ENGINE_MODE_SYMBOL_KAO_MOJI: 1590 return true; 1591 case OpenWnnJAJP.ENGINE_MODE_SYMBOL: 1592 return true; 1593 default: 1594 return (isFullView || getMaxLine() < lineCount); 1595 } 1596 } else { 1597 return (isFullView || getMaxLine() < lineCount); 1598 } 1599 } 1600 1601 /** 1602 * @return Candidate space width 1603 */ 1604 private int getCandidateSpaceWidth(boolean isEmojiSymbol) { 1605 Resources r = mWnn.getResources(); 1606 if (mPortrait) { 1607 if (isEmojiSymbol) { 1608 return 0; 1609 } else { 1610 return r.getDimensionPixelSize(R.dimen.cand_space_width); 1611 } 1612 } else { 1613 if (isEmojiSymbol) { 1614 return r.getDimensionPixelSize(R.dimen.cand_space_width_emoji_symbol); 1615 } else { 1616 return r.getDimensionPixelSize(R.dimen.cand_space_width); 1617 } 1618 } 1619 } 1620 1621 /** 1622 * KeyEvent action for the candidate view. 1623 * 1624 * @param key Key event 1625 */ 1626 public void processMoveKeyEvent(int key) { 1627 if (!mViewBody.isShown()) { 1628 return; 1629 } 1630 1631 switch (key) { 1632 case KeyEvent.KEYCODE_DPAD_UP: 1633 moveFocus(-1, true); 1634 break; 1635 1636 case KeyEvent.KEYCODE_DPAD_DOWN: 1637 moveFocus(1, true); 1638 break; 1639 1640 case KeyEvent.KEYCODE_DPAD_LEFT: 1641 moveFocus(-1, false); 1642 break; 1643 1644 case KeyEvent.KEYCODE_DPAD_RIGHT: 1645 moveFocus(1, false); 1646 break; 1647 1648 default: 1649 break; 1650 } 1651 } 1652 1653 /** 1654 * Get a flag candidate is focused now. 1655 * 1656 * @return the Candidate is focused of a flag. 1657 */ 1658 public boolean isFocusCandidate(){ 1659 if (mCurrentFocusIndex != FOCUS_NONE) { 1660 return true; 1661 } 1662 return false; 1663 } 1664 1665 /** 1666 * Give focus to View of candidate. 1667 */ 1668 public void setViewStatusOfFocusedCandidate(){ 1669 View view = mFocusedView; 1670 if (view != null) { 1671 view.setBackgroundDrawable(mFocusedViewBackground); 1672 view.setPadding(0, 0, 0, 0); 1673 } 1674 1675 TextView v = getFocusedView(); 1676 mFocusedView = v; 1677 if (v != null) { 1678 mFocusedViewBackground = v.getBackground(); 1679 v.setBackgroundResource(R.drawable.cand_back_focuse); 1680 v.setPadding(0, 0, 0, 0); 1681 1682 int viewBodyTop = getViewTopOnScreen(mViewBodyScroll); 1683 int viewBodyBottom = viewBodyTop + mViewBodyScroll.getHeight(); 1684 int focusedViewTop = getViewTopOnScreen(v); 1685 int focusedViewBottom = focusedViewTop + v.getHeight(); 1686 1687 if (focusedViewBottom > viewBodyBottom) { 1688 mViewBodyScroll.scrollBy(0, (focusedViewBottom - viewBodyBottom)); 1689 } else if (focusedViewTop < viewBodyTop) { 1690 mViewBodyScroll.scrollBy(0, (focusedViewTop - viewBodyTop)); 1691 } 1692 } 1693 } 1694 1695 /** 1696 * Clear focus to selected candidate. 1697 */ 1698 public void clearFocusCandidate(){ 1699 View view = mFocusedView; 1700 if (view != null) { 1701 view.setBackgroundDrawable(mFocusedViewBackground); 1702 view.setPadding(0, 0, 0, 0); 1703 mFocusedView = null; 1704 } 1705 1706 mFocusAxisX = 0; 1707 mHasFocusedArray1st = true; 1708 mCurrentFocusIndex = FOCUS_NONE; 1709 mHandler.removeMessages(MSG_MOVE_FOCUS); 1710 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_END)); 1711 } 1712 1713 /** 1714 * @see CandidatesViewManager#selectFocusCandidate 1715 */ 1716 public void selectFocusCandidate(){ 1717 if (mCurrentFocusIndex != FOCUS_NONE) { 1718 WnnWord word = getFocusedWnnWord(); 1719 1720 if (mHandler.hasMessages(MSG_SET_CANDIDATES)) { 1721 mWnnWordSelectedList.add(word); 1722 } else { 1723 selectCandidate(word); 1724 } 1725 } 1726 } 1727 1728 /** @see CandidatesViewManager#getFocusedWnnWord */ 1729 public WnnWord getFocusedWnnWord() { 1730 return getWnnWord(mCurrentFocusIndex); 1731 } 1732 1733 /** 1734 * Get WnnWord. 1735 * 1736 * @return WnnWord word 1737 */ 1738 public WnnWord getWnnWord(int index) { 1739 WnnWord word = null; 1740 if (index < 0) { 1741 index = 0; 1742 mHandler.removeMessages(MSG_MOVE_FOCUS); 1743 Log.i("iwnn", "TextCandidatesViewManager::getWnnWord index < 0 "); 1744 } else { 1745 int size = mHasFocusedArray1st ? mWnnWordArray1st.size() : mWnnWordArray2nd.size(); 1746 if (index >= size) { 1747 index = size - 1; 1748 mHandler.removeMessages(MSG_MOVE_FOCUS); 1749 Log.i("iwnn", "TextCandidatesViewManager::getWnnWord index > candidate max "); 1750 } 1751 } 1752 1753 if (mHasFocusedArray1st) { 1754 word = mWnnWordArray1st.get(index); 1755 } else { 1756 word = mWnnWordArray2nd.get(index); 1757 } 1758 return word; 1759 } 1760 1761 /** 1762 * Set display candidate line from SharedPreferences. 1763 */ 1764 private void setNumeberOfDisplayLines(){ 1765 SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(mWnn); 1766 mPortraitNumberOfLine = Integer.parseInt(pref.getString("setting_portrait", "2")); 1767 mLandscapeNumberOfLine = Integer.parseInt(pref.getString("setting_landscape", "1")); 1768 } 1769 1770 /** 1771 * Set emoticon enabled. 1772 */ 1773 public void setEnableEmoticon(boolean enableEmoticon) { 1774 mEnableEmoticon = enableEmoticon; 1775 } 1776 1777 /** 1778 * Get View of focus candidate. 1779 */ 1780 public TextView getFocusedView() { 1781 if (mCurrentFocusIndex == FOCUS_NONE) { 1782 return null; 1783 } 1784 TextView t; 1785 if (mHasFocusedArray1st) { 1786 t = mTextViewArray1st.get(mCurrentFocusIndex); 1787 } else { 1788 t = mTextViewArray2nd.get(mCurrentFocusIndex); 1789 } 1790 return t; 1791 } 1792 1793 /** 1794 * Move the focus to next candidate. 1795 * 1796 * @param direction The direction of increment or decrement. 1797 * @param updown {@code true} if move is up or down. 1798 */ 1799 public void moveFocus(int direction, boolean updown) { 1800 boolean isStart = (mCurrentFocusIndex == FOCUS_NONE); 1801 if (direction == 0) { 1802 setViewStatusOfFocusedCandidate(); 1803 } 1804 1805 int size1st = mTextViewArray1st.size(); 1806 if (mHasFocusedArray1st && (size1st == 0)) { 1807 mHasFocusedArray1st = false; 1808 } 1809 ArrayList<TextView> list = mHasFocusedArray1st ? mTextViewArray1st : mTextViewArray2nd; 1810 int size = list.size(); 1811 int start = (mCurrentFocusIndex == FOCUS_NONE) ? 0 : (mCurrentFocusIndex + direction); 1812 1813 int index = -1; 1814 boolean hasChangedLine = false; 1815 for (int i = start; (0 <= i) && (i < size); i += direction) { 1816 TextView view = list.get(i); 1817 if (!view.isShown()) { 1818 break; 1819 } 1820 1821 if (mIsSymbolMode && (view.getBackground() == null)) { 1822 continue; 1823 } 1824 1825 if (updown) { 1826 int left = view.getLeft(); 1827 if ((left <= mFocusAxisX) 1828 && (mFocusAxisX < view.getRight())) { 1829 index = i; 1830 break; 1831 } 1832 1833 if (left == 0) { 1834 hasChangedLine = true; 1835 } 1836 } else { 1837 index = i; 1838 break; 1839 } 1840 } 1841 1842 if ((index < 0) && hasChangedLine && !mHasFocusedArray1st && (0 < direction)) { 1843 index = mTextViewArray2nd.size() - 1; 1844 } 1845 1846 if (0 <= index) { 1847 mCurrentFocusIndex = index; 1848 setViewStatusOfFocusedCandidate(); 1849 if (!updown) { 1850 mFocusAxisX = getFocusedView().getLeft(); 1851 } 1852 } else { 1853 if (mCanReadMore && (0 < size1st)) { 1854 1855 if ((mHasFocusedArray1st && (direction < 0)) 1856 || (!mHasFocusedArray1st && (0 < direction))) { 1857 updown = false; 1858 } 1859 1860 mHasFocusedArray1st = !mHasFocusedArray1st; 1861 1862 if (!mHasFocusedArray1st && !mIsFullView) { 1863 setFullMode(); 1864 } 1865 } 1866 1867 if (size1st == 0) { 1868 updown = false; 1869 } 1870 1871 if (0 < direction) { 1872 mCurrentFocusIndex = -1; 1873 } else { 1874 mCurrentFocusIndex = (mHasFocusedArray1st ? size1st : mTextViewArray2nd.size()); 1875 } 1876 Message m = mHandler.obtainMessage(MSG_MOVE_FOCUS, direction, updown ? 1 : 0); 1877 mHandler.sendMessage(m); 1878 } 1879 1880 if (isStart) { 1881 mWnn.onEvent(new OpenWnnEvent(OpenWnnEvent.FOCUS_CANDIDATE_START)); 1882 } 1883 } 1884 1885 /** 1886 * Set hardkeyboard hidden. 1887 * 1888 * @param hardKeyboardHidden hardkeyaboard hidden. 1889 */ 1890 public void setHardKeyboardHidden(boolean hardKeyboardHidden) { 1891 mHardKeyboardHidden = hardKeyboardHidden; 1892 } 1893 1894 /** 1895 * Get view top position on screen. 1896 * 1897 * @param view target view. 1898 * @return int view top position on screen 1899 */ 1900 public int getViewTopOnScreen(View view) { 1901 int[] location = new int[2]; 1902 view.getLocationOnScreen(location); 1903 return location[1]; 1904 } 1905 1906 1907 /** @see CandidatesViewManager#setCandidateMsgRemove */ 1908 public void setCandidateMsgRemove() { 1909 mHandler.removeMessages(MSG_SET_CANDIDATES); 1910 } 1911 } 1912