diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9d6bd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,215 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..9aea6e4 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/gen/com/halbae87/koreanbasicime/BuildConfig.java b/gen/com/halbae87/koreanbasicime/BuildConfig.java new file mode 100644 index 0000000..4afbd36 --- /dev/null +++ b/gen/com/halbae87/koreanbasicime/BuildConfig.java @@ -0,0 +1,6 @@ +/** Automatically generated file. DO NOT MODIFY */ +package com.halbae87.koreanbasicime; + +public final class BuildConfig { + public final static boolean DEBUG = true; +} \ No newline at end of file diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar new file mode 100644 index 0000000..428bdbc Binary files /dev/null and b/libs/android-support-v4.jar differ diff --git a/proguard-project.txt b/proguard-project.txt new file mode 100644 index 0000000..f2fe155 --- /dev/null +++ b/proguard-project.txt @@ -0,0 +1,20 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/project.properties b/project.properties new file mode 100644 index 0000000..a3ee5ab --- /dev/null +++ b/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-17 diff --git a/res/drawable-hdpi/ic_launcher.png b/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..96a442e Binary files /dev/null and b/res/drawable-hdpi/ic_launcher.png differ diff --git a/res/drawable-hdpi/sym_keyboard_delete.png b/res/drawable-hdpi/sym_keyboard_delete.png new file mode 100644 index 0000000..5139c71 Binary files /dev/null and b/res/drawable-hdpi/sym_keyboard_delete.png differ diff --git a/res/drawable-hdpi/sym_keyboard_done.png b/res/drawable-hdpi/sym_keyboard_done.png new file mode 100644 index 0000000..471c502 Binary files /dev/null and b/res/drawable-hdpi/sym_keyboard_done.png differ diff --git a/res/drawable-hdpi/sym_keyboard_return.png b/res/drawable-hdpi/sym_keyboard_return.png new file mode 100644 index 0000000..5a5670c Binary files /dev/null and b/res/drawable-hdpi/sym_keyboard_return.png differ diff --git a/res/drawable-hdpi/sym_keyboard_search.png b/res/drawable-hdpi/sym_keyboard_search.png new file mode 100644 index 0000000..e72cde3 Binary files /dev/null and b/res/drawable-hdpi/sym_keyboard_search.png differ diff --git a/res/drawable-hdpi/sym_keyboard_shift.png b/res/drawable-hdpi/sym_keyboard_shift.png new file mode 100644 index 0000000..2757696 Binary files /dev/null and b/res/drawable-hdpi/sym_keyboard_shift.png differ diff --git a/res/drawable-hdpi/sym_keyboard_space.png b/res/drawable-hdpi/sym_keyboard_space.png new file mode 100644 index 0000000..cef2daa Binary files /dev/null and b/res/drawable-hdpi/sym_keyboard_space.png differ diff --git a/res/drawable-mdpi/ic_launcher.png b/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..359047d Binary files /dev/null and b/res/drawable-mdpi/ic_launcher.png differ diff --git a/res/drawable-mdpi/sym_keyboard_delete.png b/res/drawable-mdpi/sym_keyboard_delete.png new file mode 100644 index 0000000..6cee596 Binary files /dev/null and b/res/drawable-mdpi/sym_keyboard_delete.png differ diff --git a/res/drawable-mdpi/sym_keyboard_done.png b/res/drawable-mdpi/sym_keyboard_done.png new file mode 100644 index 0000000..c0d6d13 Binary files /dev/null and b/res/drawable-mdpi/sym_keyboard_done.png differ diff --git a/res/drawable-mdpi/sym_keyboard_return.png b/res/drawable-mdpi/sym_keyboard_return.png new file mode 100644 index 0000000..cbe2b15 Binary files /dev/null and b/res/drawable-mdpi/sym_keyboard_return.png differ diff --git a/res/drawable-mdpi/sym_keyboard_search.png b/res/drawable-mdpi/sym_keyboard_search.png new file mode 100644 index 0000000..127755d Binary files /dev/null and b/res/drawable-mdpi/sym_keyboard_search.png differ diff --git a/res/drawable-mdpi/sym_keyboard_shift.png b/res/drawable-mdpi/sym_keyboard_shift.png new file mode 100644 index 0000000..d059628 Binary files /dev/null and b/res/drawable-mdpi/sym_keyboard_shift.png differ diff --git a/res/drawable-mdpi/sym_keyboard_space.png b/res/drawable-mdpi/sym_keyboard_space.png new file mode 100644 index 0000000..09b94d9 Binary files /dev/null and b/res/drawable-mdpi/sym_keyboard_space.png differ diff --git a/res/drawable-xhdpi/ic_launcher.png b/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..71c6d76 Binary files /dev/null and b/res/drawable-xhdpi/ic_launcher.png differ diff --git a/res/layout/input.xml b/res/layout/input.xml new file mode 100644 index 0000000..e47d734 --- /dev/null +++ b/res/layout/input.xml @@ -0,0 +1,27 @@ + + + + diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml new file mode 100644 index 0000000..b5f3bc1 --- /dev/null +++ b/res/values-land/dimens.xml @@ -0,0 +1,23 @@ + + + + + 46dip + diff --git a/res/values/colors.xml b/res/values/colors.xml new file mode 100644 index 0000000..d6932a9 --- /dev/null +++ b/res/values/colors.xml @@ -0,0 +1,28 @@ + + + + #FF000000 + #FFE35900 + #ff808080 + #bbffffff + \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml new file mode 100644 index 0000000..a21ffd7 --- /dev/null +++ b/res/values/dimens.xml @@ -0,0 +1,28 @@ + + + + + 50dip + 16sp + 6sp + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml new file mode 100644 index 0000000..9a8be34 --- /dev/null +++ b/res/values/strings.xml @@ -0,0 +1,36 @@ + + + + + + 한글 기본 입력기 + + + \u0020.,;:!?\n()[]*&@{}/<>_+=|" + + + Go + Next + Send + diff --git a/res/xml/korean.xml b/res/xml/korean.xml new file mode 100644 index 0000000..d5613f0 --- /dev/null +++ b/res/xml/korean.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/korean_shifted.xml b/res/xml/korean_shifted.xml new file mode 100644 index 0000000..490d113 --- /dev/null +++ b/res/xml/korean_shifted.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/method.xml b/res/xml/method.xml new file mode 100644 index 0000000..96dee8b --- /dev/null +++ b/res/xml/method.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/res/xml/qwerty.xml b/res/xml/qwerty.xml new file mode 100644 index 0000000..7530feb --- /dev/null +++ b/res/xml/qwerty.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/symbols.xml b/res/xml/symbols.xml new file mode 100644 index 0000000..aac63fe --- /dev/null +++ b/res/xml/symbols.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/symbols_shift.xml b/res/xml/symbols_shift.xml new file mode 100644 index 0000000..e6774a7 --- /dev/null +++ b/res/xml/symbols_shift.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/halbae87/koreanbasicime/CandidateView.java b/src/com/halbae87/koreanbasicime/CandidateView.java new file mode 100644 index 0000000..48cc684 --- /dev/null +++ b/src/com/halbae87/koreanbasicime/CandidateView.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/* + * halbae87: this project is created from Soft Keyboard Sample source + */ + +package com.halbae87.koreanbasicime; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import java.util.ArrayList; +import java.util.List; + +public class CandidateView extends View { + + private static final int OUT_OF_BOUNDS = -1; + + private SoftKeyboard mService; + private List mSuggestions; + private int mSelectedIndex; + private int mTouchX = OUT_OF_BOUNDS; + private Drawable mSelectionHighlight; + private boolean mTypedWordValid; + + private Rect mBgPadding; + + private static final int MAX_SUGGESTIONS = 32; + private static final int SCROLL_PIXELS = 20; + + private int[] mWordWidth = new int[MAX_SUGGESTIONS]; + private int[] mWordX = new int[MAX_SUGGESTIONS]; + + private static final int X_GAP = 10; + + private static final List EMPTY_LIST = new ArrayList(); + + private int mColorNormal; + private int mColorRecommended; + private int mColorOther; + private int mVerticalPadding; + private Paint mPaint; + private boolean mScrolled; + private int mTargetScrollX; + + private int mTotalWidth; + + private GestureDetector mGestureDetector; + + /** + * Construct a CandidateView for showing suggested words for completion. + * @param context + * @param attrs + */ + @SuppressWarnings("deprecation") + public CandidateView(Context context) { + super(context); + mSelectionHighlight = context.getResources().getDrawable( + android.R.drawable.list_selector_background); + mSelectionHighlight.setState(new int[] { + android.R.attr.state_enabled, + android.R.attr.state_focused, + android.R.attr.state_window_focused, + android.R.attr.state_pressed + }); + + Resources r = context.getResources(); + + setBackgroundColor(r.getColor(R.color.candidate_background)); + + mColorNormal = r.getColor(R.color.candidate_normal); + mColorRecommended = r.getColor(R.color.candidate_recommended); + mColorOther = r.getColor(R.color.candidate_other); + mVerticalPadding = r.getDimensionPixelSize(R.dimen.candidate_vertical_padding); + + mPaint = new Paint(); + mPaint.setColor(mColorNormal); + mPaint.setAntiAlias(true); + mPaint.setTextSize(r.getDimensionPixelSize(R.dimen.candidate_font_height)); + mPaint.setStrokeWidth(0); + + mGestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + mScrolled = true; + int sx = getScrollX(); + sx += distanceX; + if (sx < 0) { + sx = 0; + } + if (sx + getWidth() > mTotalWidth) { + sx -= distanceX; + } + mTargetScrollX = sx; + scrollTo(sx, getScrollY()); + invalidate(); + return true; + } + }); + setHorizontalFadingEdgeEnabled(true); + setWillNotDraw(false); + setHorizontalScrollBarEnabled(false); + setVerticalScrollBarEnabled(false); + } + + /** + * A connection back to the service to communicate with the text field + * @param listener + */ + public void setService(SoftKeyboard listener) { + mService = listener; + } + + @Override + public int computeHorizontalScrollRange() { + return mTotalWidth; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int measuredWidth = resolveSize(50, widthMeasureSpec); + + // Get the desired height of the icon menu view (last row of items does + // not have a divider below) + Rect padding = new Rect(); + mSelectionHighlight.getPadding(padding); + final int desiredHeight = ((int)mPaint.getTextSize()) + mVerticalPadding + + padding.top + padding.bottom; + + // Maximum possible width and desired height + setMeasuredDimension(measuredWidth, + resolveSize(desiredHeight, heightMeasureSpec)); + } + + /** + * If the canvas is null, then only touch calculations are performed to pick the target + * candidate. + */ + @Override + protected void onDraw(Canvas canvas) { + if (canvas != null) { + super.onDraw(canvas); + } + mTotalWidth = 0; + if (mSuggestions == null) return; + + if (mBgPadding == null) { + mBgPadding = new Rect(0, 0, 0, 0); + if (getBackground() != null) { + getBackground().getPadding(mBgPadding); + } + } + int x = 0; + final int count = mSuggestions.size(); + final int height = getHeight(); + final Rect bgPadding = mBgPadding; + final Paint paint = mPaint; + final int touchX = mTouchX; + final int scrollX = getScrollX(); + final boolean scrolled = mScrolled; + final boolean typedWordValid = mTypedWordValid; + final int y = (int) (((height - mPaint.getTextSize()) / 2) - mPaint.ascent()); + + for (int i = 0; i < count; i++) { + String suggestion = mSuggestions.get(i); + float textWidth = paint.measureText(suggestion); + final int wordWidth = (int) textWidth + X_GAP * 2; + + mWordX[i] = x; + mWordWidth[i] = wordWidth; + paint.setColor(mColorNormal); + if (touchX + scrollX >= x && touchX + scrollX < x + wordWidth && !scrolled) { + if (canvas != null) { + canvas.translate(x, 0); + mSelectionHighlight.setBounds(0, bgPadding.top, wordWidth, height); + mSelectionHighlight.draw(canvas); + canvas.translate(-x, 0); + } + mSelectedIndex = i; + } + + if (canvas != null) { + if ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid)) { + paint.setFakeBoldText(true); + paint.setColor(mColorRecommended); + } else if (i != 0) { + paint.setColor(mColorOther); + } + canvas.drawText(suggestion, x + X_GAP, y, paint); + paint.setColor(mColorOther); + canvas.drawLine(x + wordWidth + 0.5f, bgPadding.top, + x + wordWidth + 0.5f, height + 1, paint); + paint.setFakeBoldText(false); + } + x += wordWidth; + } + mTotalWidth = x; + if (mTargetScrollX != getScrollX()) { + scrollToTarget(); + } + } + + private void scrollToTarget() { + int sx = getScrollX(); + if (mTargetScrollX > sx) { + sx += SCROLL_PIXELS; + if (sx >= mTargetScrollX) { + sx = mTargetScrollX; + requestLayout(); + } + } else { + sx -= SCROLL_PIXELS; + if (sx <= mTargetScrollX) { + sx = mTargetScrollX; + requestLayout(); + } + } + scrollTo(sx, getScrollY()); + invalidate(); + } + + public void setSuggestions(List suggestions, boolean completions, + boolean typedWordValid) { + clear(); + if (suggestions != null) { + mSuggestions = new ArrayList(suggestions); + } + mTypedWordValid = typedWordValid; + scrollTo(0, 0); + mTargetScrollX = 0; + // Compute the total width + onDraw(null); + invalidate(); + requestLayout(); + } + + public void clear() { + mSuggestions = EMPTY_LIST; + mTouchX = OUT_OF_BOUNDS; + mSelectedIndex = -1; + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent me) { + + if (mGestureDetector.onTouchEvent(me)) { + return true; + } + + int action = me.getAction(); + int x = (int) me.getX(); + int y = (int) me.getY(); + mTouchX = x; + + switch (action) { + case MotionEvent.ACTION_DOWN: + mScrolled = false; + invalidate(); + break; + case MotionEvent.ACTION_MOVE: + if (y <= 0) { + // Fling up!? + if (mSelectedIndex >= 0) { + mService.pickSuggestionManually(mSelectedIndex); + mSelectedIndex = -1; + } + } + invalidate(); + break; + case MotionEvent.ACTION_UP: + if (!mScrolled) { + if (mSelectedIndex >= 0) { + mService.pickSuggestionManually(mSelectedIndex); + } + } + mSelectedIndex = -1; + removeHighlight(); + requestLayout(); + break; + } + return true; + } + + /** + * For flick through from keyboard, call this method with the x coordinate of the flick + * gesture. + * @param x + */ + public void takeSuggestionAt(float x) { + mTouchX = (int) x; + // To detect candidate + onDraw(null); + if (mSelectedIndex >= 0) { + mService.pickSuggestionManually(mSelectedIndex); + } + invalidate(); + } + + private void removeHighlight() { + mTouchX = OUT_OF_BOUNDS; + invalidate(); + } +} diff --git a/src/com/halbae87/koreanbasicime/InputTables.java b/src/com/halbae87/koreanbasicime/InputTables.java new file mode 100644 index 0000000..7b5bede --- /dev/null +++ b/src/com/halbae87/koreanbasicime/InputTables.java @@ -0,0 +1,74 @@ +/* + * halbae87: this project is created from Soft Keyboard Sample source + * but this part is my original source + */ + +package com.halbae87.koreanbasicime; + +public final class InputTables { + public static final int NUM_OF_FIRST = 19; + public static final int NUM_OF_MIDDLE = 21; + public static final int NUM_OF_LAST = 27; + public static final int NUM_OF_LAST_INDEX = NUM_OF_LAST + 1; // add 1 for non-last consonant added characters + + public static final int KEYSTATE_NONE = 0; + + public static final int KEYSTATE_SHIFT = 1; + public static final int KEYSTATE_SHIFT_LEFT = 1; + public static final int KEYSTATE_SHIFT_RIGHT = 2; + public static final int KEYSTATE_SHIFT_MASK = 3; + + public static final int KEYSTATE_ALT = 4; + public static final int KEYSTATE_ALT_LEFT = 4; + public static final int KEYSTATE_ALT_RIGHT = 8; + public static final int KEYSTATE_ALT_MASK = 12; + + public static final int KEYSTATE_CTRL = 16; + public static final int KEYSTATE_CTRL_LEFT = 16; + public static final int KEYSTATE_CTRL_RIGHT = 32; + public static final int KEYSTATE_CTRL_MASK = 48; + + public static final int KEYSTATE_FN = 64; // just for future usage... + + public static final char BACK_SPACE = 0x8; + + // formula to get HANGUL_CODE by composing consonants and vowel indexes + // HANGUL_CODE = HANGUL_START + iFirst*NUM_OF_MIDDLE*NUM_OF_LAST_INDEX + iMiddle*NUM_OF_LAST_INDEX + iLast + + // getting the first consonant index from code + // iFirst = (vCode - HANGUL_START) / (NUM_OF_MIDDLE * NUM_OF_LAST_INDEX) + + // getting the vowel index from code + // iMiddle = ((vCode - HANGUL_START) % (NUM_OF_MIDDLE * NUM_OF_LAST_INDEX)) / NUM_OF_LAST_INDEX + + // getting the last consonant index from code + // iLast = (vCode - HANGUL_START) % NUM_OF_LAST_INDEX + + + public static final class NormalKeyMap { + public static final char Code[] = {0x3141, 0x3160, 0x314A, 0x3147, 0x3137, 0x3139, 0x314E, 0x3157, 0x3151, 0x3153, 0x314F, 0x3163, 0x3161, 0x315C, 0x3150, 0x3154, 0x3142, 0x3131, 0x3134, 0x3145, 0x3155, 0x314D, 0x3148, 0x314C, 0x315B, 0x314B}; + public static final int FirstIndex[] = {6, -1, 14, 11, 3, 5, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, 0, 2, 9, -1, 17, 12, 16, -1, 15}; + public static final int MiddleIndex[] = {-1, 17, -1, -1, -1, -1, -1, 8, 2, 4, 0, 20, 18, 13, 1, 5, -1, -1, -1, -1, 6, -1, -1, -1, 12, -1}; + public static final int LastIndex[] = {16, -1, 23, 21, 7, 8, 27, -1, -1, -1, -1, -1, -1, -1, -1, -1, 17, 1, 4, 19, -1, 26, 22, 25, -1, 24}; + } + + public static final class ShiftedKeyMap { + public static final char Code[] = {0x3141, 0x3160, 0x314A, 0x3147, 0x3138, 0x3139, 0x314E, 0x3157, 0x3151, 0x3153, 0x314F, 0x3163, 0x3161, 0x315C, 0x3152, 0x3156, 0x3143, 0x3132, 0x3134, 0x3146, 0x3155, 0x314D, 0x3149, 0x314C, 0x315B, 0x314B}; + public static final int FirstIndex[] = {6, -1, 14, 11, 4, 5, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 1, 2, 10, -1, 17, 13, 16, -1, 15}; + public static final int MiddleIndex[] = {-1, 17, -1, -1, -1, -1, -1, 8, 2, 4, 0, 20, 18, 13, 3, 7, -1, -1, -1, -1, 6, -1, -1, -1, 12, -1}; + public static final int LastIndex[] = {16, -1, 23, 21, -1, 8, 27, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 2, 4, 20, -1, 26, -1, 25, -1, 24}; + } + + public static final char FirstConsonantCodes[] = {0x3131, 0x3132, 0x3134, 0x3137, 0x3138, 0x3139, 0x3141, 0x3142, 0x3143, 0x3145, 0x3146, 0x3147, 0x3148, 0x3149, 0x314A, 0x314B, 0x314C, 0x314D, 0x314E }; + + public static final class LastConsonants { + public static final char Code[] = {0x0, 0x3131, 0x3132, 0x3133, 0x3134, 0x3135, 0x3136, 0x3137, 0x3139, 0x313A, 0x313B, 0x313C, 0x313D, 0x313E, 0x313F, 0x3140, 0x3141, 0x3142, 0x3144, 0x3145, 0x3146, 0x3147, 0x3148, 0x314A, 0x314B, 0x314C, 0x314D, 0x314E}; + public static final int iLast[] = {-1, -1, -1, 1, -1, 4, 4, -1, -1, 8, 8, 8, 8, 8, 8, 8, -1, -1, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + public static final int iFirst[] = {-1, -1, -1, 9, -1, 12, 18, -1, -1, 0, 6, 7, 9, 16, 17, 18, -1, -1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + } + + public static final class Vowels { + public static final char Code[] = {0x314F, 0x3150, 0x3151, 0x3152, 0x3153, 0x3154, 0x3155, 0x3156, 0x3157, 0x3158, 0x3159, 0x315A, 0x315B, 0x315C, 0x315D, 0x315E, 0x315F, 0x3160, 0x3161, 0x3162, 0x3163}; + public static final int iMiddle[] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 8, 8, -1, -1, 13, 13, 13, -1, -1, 18, -1}; + } +} diff --git a/src/com/halbae87/koreanbasicime/KoreanAutomata.java b/src/com/halbae87/koreanbasicime/KoreanAutomata.java new file mode 100644 index 0000000..e7a9302 --- /dev/null +++ b/src/com/halbae87/koreanbasicime/KoreanAutomata.java @@ -0,0 +1,1142 @@ +/* + * halbae87: this project is created from Soft Keyboard Sample source + * but this part is my original source. + */ + +package com.halbae87.koreanbasicime; + +import com.halbae87.koreanbasicime.InputTables; +import android.util.Log; + +public class KoreanAutomata { + public static int HANGUL_START = 0xAC00; + public static int HANGUL_END = 0xD7A3; + public static int HANGUL_JAMO_START = 0x3131; + public static int HANGUL_MO_START = 0x314F; + public static int HANGUL_JAMO_END = 0x3163; + private static final String TAG = "KoreanAutomata"; + + // Action Codes + public static final int ACTION_NONE = 0; + public static final int ACTION_UPDATE_COMPOSITIONSTR = 1; + public static final int ACTION_UPDATE_COMPLETESTR = 2; + public static final int ACTION_USE_INPUT_AS_RESULT = 4; + // public static final int ACTION_BACKSPACE = 8; not used. + public static final int ACTION_ERROR = -1; + + private int mState = 0; + private String mCompositionString = ""; + private String mCompleteString = ""; + private boolean mKoreanMode = false; + + public KoreanAutomata() + { + mState = 0; + mCompositionString = ""; + mCompleteString = ""; + mKoreanMode = false; + } + public int GetState() + { + return mState; + }; + public String GetCompositionString() + { + return mCompositionString; + }; + public String GetCompleteString() + { + return mCompleteString; + }; + + public void ToggleMode() + { + mKoreanMode = !mKoreanMode; + } + public boolean IsKoreanMode() + { + return mKoreanMode; + } + public boolean IsHangul(char code) + { + if ((code >= HANGUL_START) && (code <= HANGUL_END)) + return true; + if ((code >= HANGUL_JAMO_START) && (code <= HANGUL_JAMO_END)) + return true; + return false; + } + + public boolean IsJAMO(char code) + { + if ((code >= HANGUL_JAMO_START) && (code <= HANGUL_JAMO_END)) + return true; + return false; + }; + + public boolean IsConsonant(char code) + { + if ((code >= HANGUL_JAMO_START) && (code < HANGUL_MO_START)) + return true; + return false; + }; + + public boolean IsVowel(char code) + { + if ((code >= HANGUL_MO_START) && (code <= HANGUL_JAMO_END)) + return true; + return false; + }; + + /** not used. + public boolean IsLastConsonanted(char code) + { + if (IsHangul(code)) + { + if (IsJAMO(code)) // <- need to fix, if this routine is to be used... + return true; + int offset = code - HANGUL_START; + if (offset % InputTables.NUM_OF_LAST_INDEX == 0) + return false; + else + return true; + } + else + { + // wrong input + return false; + } + } + **/ + + public int GetLastConsonantIndex(char code) + { + int lcIndex= -1; + if (IsHangul(code)) + { + if (IsJAMO(code)) + { + if (IsConsonant(code)) + { + for (lcIndex = 0; lcIndex < InputTables.NUM_OF_LAST_INDEX; lcIndex++) + { + if (code == InputTables.LastConsonants.Code[lcIndex]) + break; + } + if (lcIndex >= InputTables.NUM_OF_LAST_INDEX) + lcIndex = -1; + } + else + lcIndex = -1; + } + else + { + int offset = code - HANGUL_START; + lcIndex = (offset % InputTables.NUM_OF_LAST_INDEX); + } + } + return lcIndex; + } + + public char GetLastConsonant(char code) + { + char lcCode; + int lcIndex = GetLastConsonantIndex(code); + if (lcIndex < 0) + lcCode = (char) 0; + else + lcCode = InputTables.LastConsonants.Code[lcIndex]; + return lcCode; + } + + public int GetFirstConsonantIndex(char code) + { + int fcIndex = -1; + if (IsHangul(code)) + { + if (IsConsonant(code)) + { + for (fcIndex = 0; fcIndex < InputTables.NUM_OF_FIRST; fcIndex++) + if (code == InputTables.FirstConsonantCodes[fcIndex]) + break; + if (fcIndex >= InputTables.NUM_OF_FIRST) + fcIndex = -1; + } + else if (IsVowel(code)) + { + fcIndex = -1; + } + else + { + int offset = code - HANGUL_START; + fcIndex = (offset / (InputTables.NUM_OF_MIDDLE * InputTables.NUM_OF_LAST_INDEX)); + } + } + + return fcIndex; + } + + public char GetFirstConsonant(char code) + { + char fcCode; + int fcIndex = GetFirstConsonantIndex(code); + if (fcIndex < 0) + fcCode = (char) 0; + else + fcCode = InputTables.FirstConsonantCodes[fcIndex]; + return fcCode; + } + + public int GetVowelIndex(char code) + { + int vIndex = -1; + if (IsHangul(code)) + { + if (IsVowel(code)) // vowel only character.. + { + vIndex = ConvertVowelCodeToIndex(code); + } + else + { + int offset = code - HANGUL_START; + vIndex = (offset % (InputTables.NUM_OF_MIDDLE * InputTables.NUM_OF_LAST_INDEX))/InputTables.NUM_OF_LAST_INDEX; + } + } + return vIndex; + } + + public char GetVowel(char code) + { + char vCode; + int vIndex = GetVowelIndex(code); + if (vIndex < 0) + vCode = (char) 0; + else + vCode = InputTables.Vowels.Code[vIndex]; + return vCode; + } + + public int ConvertFirstConsonantCodeToIndex(char fcCode) // fcCode should be one of "First Consonants" otherwise return -1 + { + int fcIndex = 0; + while (fcIndex < InputTables.NUM_OF_FIRST) + { + if (fcCode == InputTables.FirstConsonantCodes[fcIndex]) + break; + fcIndex++; + } + if (fcIndex == InputTables.NUM_OF_FIRST) + fcIndex = -1; + return fcIndex; + } + + public int ConvertLastConsonantCodeToIndex(char lcCode) // fcCode should be one of "Last Consonants", otherwise return -1 + { + int lcIndex = 0; + while (lcIndex < InputTables.NUM_OF_LAST_INDEX) + { + if (lcCode == InputTables.LastConsonants.Code[lcIndex]) + break; + lcIndex++; + } + if (lcIndex == InputTables.NUM_OF_LAST_INDEX) + lcIndex = -1; + return lcIndex; + } + + public int ConvertVowelCodeToIndex(char vCode) + { + if (vCode < InputTables.Vowels.Code[0]) + return -1; + int vIndex = vCode - InputTables.Vowels.Code[0]; + if (vIndex >= InputTables.NUM_OF_MIDDLE) + return -1; + return vIndex; + } + + public int CombineLastConsonantWithIndex(int cIndex1, int cIndex2) + { + int newIndex = 0; + char newCode = (char) 0; + + if (InputTables.LastConsonants.Code[cIndex1] == 0x3131 && InputTables.LastConsonants.Code[cIndex2] == 0x3145) + newCode = 0x3133; // ㄳ + + if (InputTables.LastConsonants.Code[cIndex1] == 0x3142 && InputTables.LastConsonants.Code[cIndex2] == 0x3145) + newCode = 0x3144; // ã…„ + + if (InputTables.LastConsonants.Code[cIndex1] == 0x3134) + { + if (InputTables.LastConsonants.Code[cIndex2] == 0x3148) + newCode = 0x3135; // ㄵ + else if (InputTables.LastConsonants.Code[cIndex2] == 0x314E) + newCode = 0x3136; // ㄶ + } + + if (InputTables.LastConsonants.Code[cIndex1] == 0x3139) + { + if (InputTables.LastConsonants.Code[cIndex2] == 0x3131) + newCode = 0x313A; // ㄺ + else if (InputTables.LastConsonants.Code[cIndex2] == 0x3141) + newCode = 0x313B; // ã„» + else if (InputTables.LastConsonants.Code[cIndex2] == 0x3142) + newCode = 0x313C; // ㄼ + else if (InputTables.LastConsonants.Code[cIndex2] == 0x3145) + newCode = 0x313D; // ㄽ + else if (InputTables.LastConsonants.Code[cIndex2] == 0x314C) + newCode = 0x313E; // ㄾ + else if (InputTables.LastConsonants.Code[cIndex2] == 0x314D) + newCode = 0x313F; // ã„¿ + else if (InputTables.LastConsonants.Code[cIndex2] == 0x314E) + newCode = 0x3140; // ã…€ + } + + if (newCode == (char) 0) + newIndex = -1; + else + newIndex = ConvertLastConsonantCodeToIndex(newCode); + + return newIndex; + } + + public char CombineLastConsonantWithCode(char lcCode1, char lcCode2) + { + char newCode = (char) 0; + + if (lcCode1 == 0x3131 && lcCode2 == 0x3145) + newCode = 0x3133; // ㄳ + + else if (lcCode1 == 0x3142 && lcCode2 == 0x3145) + newCode = 0x3144; // ã…„ + + else if (lcCode1 == 0x3134) + { + if (lcCode2 == 0x3148) + newCode = 0x3135; // ㄵ + else if (lcCode2 == 0x314E) + newCode = 0x3136; // ㄶ + } + + else if (lcCode1 == 0x3139) + { + if (lcCode2 == 0x3131) + newCode = 0x313A; // ㄺ + else if (lcCode2 == 0x3141) + newCode = 0x313B; // ã„» + else if (lcCode2 == 0x3142) + newCode = 0x313C; // ㄼ + else if (lcCode2 == 0x3145) + newCode = 0x313D; // ㄽ + else if (lcCode2 == 0x314C) + newCode = 0x313E; // ㄾ + else if (lcCode2 == 0x314D) + newCode = 0x313F; // ã„¿ + else if (lcCode2 == 0x314E) + newCode = 0x3140; // ã…€ + } + + return newCode; + } + + public char CombineVowelWithCode(char vCode1, char vCode2) + { + char newCode = (char) 0; + if (vCode1 == 0x3157) // ã…— + { + if (vCode2 == 0x314F) // ã…� + newCode = 0x3158; // ã…˜ + else if (vCode2 == 0x3150) // ã…� + newCode = 0x3159; // ã…™ + else if (vCode2 == 0x3163) // ã…£ + newCode = 0x315A; // ã…š + } + else if (vCode1 == 0x315C) // ã…œ + { + if (vCode2 == 0x3153) // ã…“ + newCode = 0x315D; // ã…� + else if (vCode2 == 0x3154) // ã…” + newCode = 0x315E; // ã…ž + else if (vCode2 == 0x3163) // ã…£ + newCode = 0x315F; // ã…Ÿ + } + else if (vCode1 == 0x3161) // ã…¡ + { + if (vCode2 == 0x3163) // ã…£ + newCode = 0x3162; // ã…¢ + } + return newCode; + } + + public int CombineVowelWithIndex(int vIndex1, int vIndex2) + { + int newIndex = -1; + char vCode1 = InputTables.Vowels.Code[vIndex1]; + char vCode2 = InputTables.Vowels.Code[vIndex2]; + + char newCode = CombineVowelWithCode(vCode1, vCode2); + if (newCode != (char) 0) + { + newIndex = ConvertVowelCodeToIndex(newCode); + } + return newIndex; + } + + public char ComposeCharWithIndexs(int fcIndex, int vIndex, int lcIndex) + { + char Code = (char) 0; + if ((fcIndex >= 0) && (fcIndex < InputTables.NUM_OF_FIRST)) + { + if ((vIndex >= 0) && (vIndex < InputTables.NUM_OF_MIDDLE)) + { + if ((lcIndex >= 0) && (lcIndex < InputTables.NUM_OF_LAST)) + { + int offset = fcIndex*InputTables.NUM_OF_MIDDLE*InputTables.NUM_OF_LAST_INDEX + vIndex*InputTables.NUM_OF_LAST_INDEX + lcIndex; + Code = (char) (offset + HANGUL_START); + } + } + } + return Code; + } + + public int GetAlphabetIndex(char code) + { + if (code >= 'a' && code <= 'z') + return (int) (code - 'a'); + if (code >= 'A' && code <= 'Z') + return (int) (code - 'A'); + return -1; + } + + /* not used... + public boolean IsToggleKey(char code, int KeyState) + { + boolean bRet = false; + if ((code == ' ') && ((KeyState & InputTables.KEYSTATE_SHIFT_MASK) != 0)) // SHIFT-SPACE + bRet = true; + return bRet; + } + */ + + public int DoBackSpace() + { + int ret = ACTION_NONE; + char code; + + if (mCompositionString != "") + code = mCompositionString.charAt(0); + else + code = (char) 0; + + if (mState != 0 && code == (char) 0) + { + // Log.v(TAG, "DoBackSpace -- Error. CompositionString is NULL. mState = " + mState); + return ACTION_ERROR; + } + + switch (mState) + { + case 0: // current composition string: NULL + ret = ACTION_USE_INPUT_AS_RESULT; + break; + case 1: // current composition string: single consonant only + case 4: // current composition string: single vowel + mCompositionString = ""; + mState = 0; + ret = ACTION_UPDATE_COMPOSITIONSTR; + break; + + case 2: // current composition string: single consonant + single vowel + // iFirst = (vCode - HANGUL_START) / (NUM_OF_MIDDLE * NUM_OF_LAST_INDEX) + { + int fcIndex = GetFirstConsonantIndex(code); + code = InputTables.FirstConsonantCodes[fcIndex]; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + } + ret = ACTION_UPDATE_COMPOSITIONSTR; + break; + case 3: // current composition string: single consonant + single vowel + single consonant + // iLast = (vCode - HANGUL_START) % NUM_OF_LAST_INDEX + { + int lcIndex = GetLastConsonantIndex(code); + code = (char)((int)code - lcIndex); + mCompositionString = ""; + mCompositionString += code; + mState = 2; + } + ret = ACTION_UPDATE_COMPOSITIONSTR; + break; + case 5: // current composition string: a combined vowel + { + int vIndex = GetVowelIndex(code); + if (vIndex < 0) + { + ret = ACTION_ERROR; + break; + } + int newIndex = InputTables.Vowels.iMiddle[vIndex]; + if (newIndex < 0) + { + ret = ACTION_ERROR; + break; + } + code = InputTables.Vowels.Code[newIndex]; + mCompositionString = ""; + mCompositionString += code; + mState = 4; + } + ret = ACTION_UPDATE_COMPOSITIONSTR; + break; + case 10: // current composition string: a combined consonant + { + int lcIndex = GetLastConsonantIndex(code); + if (lcIndex < 0) + { + ret = ACTION_ERROR; + break; + } + int newIndex = InputTables.LastConsonants.iLast[lcIndex]; + if (newIndex < 0) + { + ret = ACTION_ERROR; + break; + } + code = InputTables.LastConsonants.Code[newIndex]; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + } + ret = ACTION_UPDATE_COMPOSITIONSTR; + break; + case 11: // current composition string: single consonant + single vowel + a combined consonant + { + int lcIndex = GetLastConsonantIndex(code); + if (lcIndex < 0) + { + ret = ACTION_ERROR; + break; + } + int newIndex = InputTables.LastConsonants.iLast[lcIndex]; + if (newIndex < 0) + { + ret = ACTION_ERROR; + break; + } + code = (char)((int) code - lcIndex + newIndex); + mCompositionString = ""; + mCompositionString += code; + mState = 3; + } + ret = ACTION_UPDATE_COMPOSITIONSTR; + break; + case 20: // current composition string: single consonant + a combined vowel + { + int fcIndex = GetFirstConsonantIndex(code); + int vIndex = GetVowelIndex(code); + int newIndex = InputTables.Vowels.iMiddle[vIndex]; + if (newIndex < 0) + { + ret = ACTION_ERROR; + break; + } + code = ComposeCharWithIndexs(fcIndex, newIndex, 0); + mCompositionString = ""; + mCompositionString += code; + mState = 2; + } + ret = ACTION_UPDATE_COMPOSITIONSTR; + break; + case 21: // current composition string: single consonant + a combined vowel + single consonant + { + int lcIndex = GetLastConsonantIndex(code); + code = (char) ((int) code - lcIndex); + mCompositionString = ""; + mCompositionString += code; + mState = 20; + } + ret = ACTION_UPDATE_COMPOSITIONSTR; + break; + case 22: // current composition string: single consonant + a combined vowel + a combined consonant + { + int lcIndex = GetLastConsonantIndex(code); + if (lcIndex < 0) + { + ret = ACTION_ERROR; + break; + } + int newIndex = InputTables.LastConsonants.iLast[lcIndex]; + if (newIndex < 0) + { + ret = ACTION_ERROR; + break; + } + code = (char)((int) code - lcIndex + newIndex); + mCompositionString = ""; + mCompositionString += code; + mState = 21; + } + ret = ACTION_UPDATE_COMPOSITIONSTR; + break; + default: ret = ACTION_ERROR; // error. should not be here in any circumstance. + } + return ret; + } + + public int FinishAutomataWithoutInput() // Input is ended by external causes + { + int ret = ACTION_NONE; + if (mKoreanMode) // && mState > 0) + { + mCompleteString = ""; + mCompositionString = ""; + mState = 0; + //ret |= ACTION_UPDATE_COMPOSITIONSTR; + //ret |= ACTION_UPDATE_COMPLETESTR; + } + return ret; + } + + public int DoAutomata(char code, int KeyState) // , String CurrentCompositionString) + { + // Log.v(TAG, "DoAutomata Entered - code = "+ code + " KeyState = " + KeyState + " mState = " + mState); + + int result = ACTION_NONE; + int AlphaIndex = GetAlphabetIndex(code); + char hcode; + + /* remove toggle key check and backspace check. + // check toggle key first + if (IsToggleKey(code, KeyState)) // SHIFT-SPACE + { + // toggle Korean/English + if (mState != 0) // flushing.. + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mState = 0; + result = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + mKoreanMode = !mKoreanMode; // input mode toggle + } + else if (code == InputTables.BACK_SPACE) + { + // do back space + } + else */ + if (AlphaIndex < 0) // white spaces... + { + if (mKoreanMode) + { + // flush Korean characters first. + mCompleteString = mCompositionString; + mCompositionString = ""; + mState = 0; + result = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + // process the code as English + if ((KeyState & (InputTables.KEYSTATE_ALT_MASK | InputTables.KEYSTATE_CTRL_MASK | InputTables.KEYSTATE_FN)) == 0) + { + result |= ACTION_USE_INPUT_AS_RESULT; + } + } + else if (!mKoreanMode){ + // process the code as English + result = ACTION_USE_INPUT_AS_RESULT; + } + else { + if ((KeyState & InputTables.KEYSTATE_SHIFT_MASK) == 0) + { + hcode = InputTables.NormalKeyMap.Code[AlphaIndex]; + } + else + { + hcode = InputTables.ShiftedKeyMap.Code[AlphaIndex]; + } + // Log.v(TAG, "--DoAutomata() - hcode = " + hcode); + + switch (mState) + { + case 0: result = DoState00(hcode); break; // current composition string: NULL + case 1: result = DoState01(hcode); break; // current composition string: single consonant only + case 2: result = DoState02(hcode); break; // current composition string: single consonant + single vowel + case 3: result = DoState03(hcode); break; // current composition string: single consonant + single vowel + single consonant + case 4: result = DoState04(hcode); break; // current composition string: single vowel + case 5: result = DoState05(hcode); break; // current composition string: a combined vowel + case 10: result = DoState10(hcode); break; // current composition string: a combined consonant + case 11: result = DoState11(hcode); break; // current composition string: single consonant + single vowel + a combined consonant + case 20: result = DoState20(hcode); break; // current composition string: single consonant + a combined vowel + case 21: result = DoState21(hcode); break; // current composition string: single consonant + a combined vowel + single consonant + case 22: result = DoState22(hcode); break; // current composition string: single consonant + a combined vowel + a combined consonant + default: result = ACTION_ERROR; // error. should not be here in any circumstance. + } + } + return result; + }; + + private int DoState00(char code) // current composition string: NULL + { + // Log.v(TAG, "State 0 Entered - code = "+ code ); + if (IsConsonant(code)) + { + mState = 1; + } + else + { + mState = 4; + } + mCompleteString = ""; + mCompositionString = ""; + mCompositionString += code; + return ACTION_UPDATE_COMPOSITIONSTR; + }; + + private int DoState01(char code) // current composition string: single consonant only + { + // Log.v(TAG, "State 1 Entered - code = "+ code ); + if (mCompositionString == "") + { + // Log.v(TAG, "DoState 01 -- Error. CompositionString is NULL"); + return ACTION_ERROR; + } + + int ret = ACTION_NONE; + if (IsConsonant(code)) + { + char newCode = CombineLastConsonantWithCode(mCompositionString.charAt(0), code); + if (newCode == (char)0) // cannot combine last consonants + { + mCompleteString = mCompositionString; // flush + mCompositionString = ""; + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + else // can combine last consonants + { + mCompleteString = ""; + mCompositionString = ""; + mCompositionString += newCode; + mState = 10; + ret = ACTION_UPDATE_COMPOSITIONSTR; + } + } + else + { + int fcIndex = ConvertFirstConsonantCodeToIndex(mCompositionString.charAt(0)); + int vIndex = ConvertVowelCodeToIndex(code); + char newCode = ComposeCharWithIndexs(fcIndex, vIndex, 0); + mCompleteString = ""; + mCompositionString = ""; + mCompositionString += newCode; + mState = 2; + ret = ACTION_UPDATE_COMPOSITIONSTR; + } + return ret; + }; + + private int DoState02(char code) // current composition string: single consonant + single vowel + { + // Log.v(TAG, "State 2 Entered - code = "+ code ); + if (mCompositionString == "") + { + // Log.v(TAG, "DoState-02 -- Error. CompositionString is NULL"); + return ACTION_ERROR; + } + + int ret = ACTION_NONE; + if (IsConsonant(code)) + { + int lcIndex = GetLastConsonantIndex(code); + if (lcIndex != -1) // code can be last consonant.. + { + char newCode = (char)((int) mCompositionString.charAt(0) + lcIndex); + mCompleteString = ""; + mCompositionString = ""; + mCompositionString += newCode; + mState = 3; + ret = ACTION_UPDATE_COMPOSITIONSTR; + } + else + { + mCompleteString = mCompositionString; + mCompositionString = ""; // flush + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + } + else // vowel + { + char vCode = GetVowel(mCompositionString.charAt(0)); + char newCode = CombineVowelWithCode(vCode, code); + if (newCode != (char) 0) + { + int fcIndex = GetFirstConsonantIndex(mCompositionString.charAt(0)); + int vIndex = ConvertVowelCodeToIndex(newCode); + char newChar = ComposeCharWithIndexs(fcIndex, vIndex, 0); + mCompleteString = ""; + mCompositionString = ""; + mCompositionString += newChar; + mState = 20; + ret = ACTION_UPDATE_COMPOSITIONSTR; + } + else + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 4; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + } + return ret; + }; + + private int DoState03(char code) // current composition string: single consonant + single vowel + single consonant + { + // Log.v(TAG, "State 3 Entered - code = "+ code ); + if (mCompositionString == "") + { + // Log.v(TAG, "DoState 03 -- Error. CompositionString is NULL"); + return ACTION_ERROR; + } + + int ret = ACTION_NONE; + if (IsConsonant(code)) + { + int lcIndex = GetLastConsonantIndex(mCompositionString.charAt(0)); + if (lcIndex < 0) + { + // Log.v(TAG, " -- Error. consonant, lcIndex = " + lcIndex); + return ACTION_ERROR; + } + char newCode = CombineLastConsonantWithCode(InputTables.LastConsonants.Code[lcIndex], code); + if (newCode != (char)0) // Last Consonants can be combined + { + char newChar = (char) ((int) mCompositionString.charAt(0) - lcIndex + GetLastConsonantIndex(newCode)); + mCompleteString = ""; + mCompositionString = ""; + mCompositionString += newChar; + mState = 11; + ret = ACTION_UPDATE_COMPOSITIONSTR; + } + else + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + } + else // vowel + { + int lcIndex = GetLastConsonantIndex(mCompositionString.charAt(0)); + if (lcIndex < 0) + { + // Log.v(TAG, " -- complete Error. vowel, lcIndex = " + lcIndex); + return ACTION_ERROR; + } + char newChar = (char) ((int) mCompositionString.charAt(0) - lcIndex); // remove last consonant and flush it. + mCompleteString = ""; + mCompleteString += newChar; + int fcIndex = GetFirstConsonantIndex(InputTables.LastConsonants.Code[lcIndex]); + if (fcIndex < 0) + { + // Log.v(TAG, " -- composition Error, vowel, lcIndex = " + lcIndex); + return ACTION_ERROR; + } + int vIndex = GetVowelIndex(code); + char newCode = ComposeCharWithIndexs(fcIndex, vIndex, 0); // compose new composition string + mCompositionString = ""; + mCompositionString += newCode; + mState = 2; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + return ret; + }; + + private int DoState04(char code) // current composition string: single vowel + { + // Log.v(TAG, "State 4 Entered - code = "+ code ); + if (mCompositionString == "") + { + // Log.v(TAG, "DoState 04 -- Error. CompositionString is NULL"); + return ACTION_ERROR; + } + + int ret = ACTION_NONE; + if (IsConsonant(code)) + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + else + { + char newCode = CombineVowelWithCode(mCompositionString.charAt(0), code); + if (newCode != (char) 0) + { + mCompleteString = ""; + mCompositionString = ""; + mCompositionString += newCode; + mState = 5; + ret = ACTION_UPDATE_COMPOSITIONSTR; + } + else + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 4; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + } + return ret; + }; + + private int DoState05(char code) // current composition string: a combined vowel + { + // Log.v(TAG, "State 5 Entered - code = "+ code ); + if (mCompositionString == "") + { + // Log.v(TAG, "DoState 05 -- Error. CompositionString is NULL"); + return ACTION_ERROR; + } + + int ret = ACTION_NONE; + if (IsConsonant(code)) + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + else + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 4; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + return ret; + }; + + private int DoState10(char code) // current composition string: a combined consonant + { + // Log.v(TAG, "State 10 Entered - code = "+ code ); + if (mCompositionString == "") + { + // Log.v(TAG, "DoState 10 -- Error. CompositionString is NULL"); + return ACTION_ERROR; + } + + int ret = ACTION_NONE; + if (IsConsonant(code)) + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + else + { + int lcIndex0 = GetLastConsonantIndex(mCompositionString.charAt(0)); + int lcIndex1 = InputTables.LastConsonants.iLast[lcIndex0]; + int fcIndex = InputTables.LastConsonants.iFirst[lcIndex0]; + mCompleteString = ""; + mCompleteString += InputTables.LastConsonants.Code[lcIndex1]; + int vIndex = GetVowelIndex(code); + char newChar = ComposeCharWithIndexs(fcIndex, vIndex, 0); + mCompositionString = ""; + mCompositionString += newChar; + mState = 2; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + return ret; + }; + + private int DoState11(char code) // current composition string: single consonant + single vowel + a combined consonant + { + // Log.v(TAG, "State 11 Entered - code = "+ code ); + if (mCompositionString == "") + { + // Log.v(TAG, "DoState 11 -- Error. CompositionString is NULL"); + return ACTION_ERROR; + } + + int ret = ACTION_NONE; + if (IsConsonant(code)) + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + else + { + int lcIndexOrg = GetLastConsonantIndex(mCompositionString.charAt(0)); + int fcIndexOrg = GetFirstConsonantIndex(mCompositionString.charAt(0)); + int vIndexOrg = GetVowelIndex(mCompositionString.charAt(0)); + int lcIndexNew = InputTables.LastConsonants.iLast[lcIndexOrg]; + char newChar = ComposeCharWithIndexs(fcIndexOrg, vIndexOrg, lcIndexNew); + int fcIndexNew = InputTables.LastConsonants.iFirst[lcIndexOrg]; + int vIndexNew = ConvertVowelCodeToIndex(code); + mCompleteString = ""; + mCompleteString += newChar; + newChar = ComposeCharWithIndexs(fcIndexNew, vIndexNew, 0); + mCompositionString = ""; + mCompositionString += newChar; + mState = 2; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + return ret; + }; + + private int DoState20(char code) // current composition string: single consonant + a combined vowel + { + // Log.v(TAG, "State 20 Entered - code = "+ code ); + if (mCompositionString == "") + { + // Log.v(TAG, "DoState 20 -- Error. CompositionString is NULL"); + return ACTION_ERROR; + } + + int ret = ACTION_NONE; + if (IsConsonant(code)) + { + int lcIndex = ConvertLastConsonantCodeToIndex(code); + if (lcIndex < 0) // cannot compose the code with composition string. flush it. + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + else // compose.. + { + char newChar = mCompositionString.charAt(0); + newChar = (char)((int)newChar + lcIndex); + mCompleteString = ""; + mCompositionString = ""; + mCompositionString += newChar; + mState = 21; + ret = ACTION_UPDATE_COMPOSITIONSTR; + } + } + else + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 4; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + return ret; + }; + + private int DoState21(char code) // current composition string: single consonant + a combined vowel + single consonant + { + // Log.v(TAG, "State 21 Entered - code = "+ code ); + if (mCompositionString == "") + { + // Log.v(TAG, "DoState 20 -- Error. CompositionString is NULL"); + return ACTION_ERROR; + } + + int ret = ACTION_NONE; + if (IsConsonant(code)) + { + int lcIndex = GetLastConsonantIndex(mCompositionString.charAt(0)); + int lcIndexTemp = ConvertLastConsonantCodeToIndex(code); + if (lcIndexTemp < 0) + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + else { + int lcIndexNew = CombineLastConsonantWithIndex(lcIndex, lcIndexTemp); + if (lcIndexNew < 0) + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + else + { + char newChar = mCompositionString.charAt(0); + newChar = (char)((int)newChar - lcIndex + lcIndexNew); + mCompleteString = ""; + mCompositionString = ""; + mCompositionString += newChar; + mState = 22; + ret = ACTION_UPDATE_COMPOSITIONSTR; + } + } + + } + else + { + char newChar = mCompositionString.charAt(0); + int lcIndex = GetLastConsonantIndex(newChar); + newChar = (char)((int)newChar - lcIndex); + mCompleteString = ""; + mCompleteString += newChar; + int fcIndex = ConvertFirstConsonantCodeToIndex(InputTables.LastConsonants.Code[lcIndex]); + int vIndex = ConvertVowelCodeToIndex(code); + newChar = ComposeCharWithIndexs(fcIndex, vIndex, 0); + mCompositionString = ""; + mCompositionString += newChar; + mState = 2; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + return ret; + }; + + private int DoState22(char code) // current composition string: single consonant + a combined vowel + a combined consonant + { + // Log.v(TAG, "State 22 Entered - code = "+ code ); + if (mCompositionString == "") + { + // Log.v(TAG, "DoState 22 -- Error. CompositionString is NULL"); + return ACTION_ERROR; + } + + int ret = ACTION_NONE; + if (IsConsonant(code)) + { + mCompleteString = mCompositionString; + mCompositionString = ""; + mCompositionString += code; + mState = 1; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + else + { + int lcIndex0 = GetLastConsonantIndex(mCompositionString.charAt(0)); + int lcIndex1 = InputTables.LastConsonants.iLast[lcIndex0]; + int fcIndex = InputTables.LastConsonants.iFirst[lcIndex0]; + mCompleteString = ""; + mCompleteString += InputTables.LastConsonants.Code[lcIndex1]; + int vIndex = GetVowelIndex(code); + char newChar = ComposeCharWithIndexs(fcIndex, vIndex, 0); + mCompositionString = ""; + mCompositionString += newChar; + mState = 2; + ret = ACTION_UPDATE_COMPLETESTR | ACTION_UPDATE_COMPOSITIONSTR; + } + return ret; + }; +} diff --git a/src/com/halbae87/koreanbasicime/LatinKeyboard.java b/src/com/halbae87/koreanbasicime/LatinKeyboard.java new file mode 100644 index 0000000..b6efce1 --- /dev/null +++ b/src/com/halbae87/koreanbasicime/LatinKeyboard.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/* + * halbae87: this project is created from Soft Keyboard Sample source + */ +package com.halbae87.koreanbasicime; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.Keyboard.Key; +import android.inputmethodservice.Keyboard.Row; +import android.view.inputmethod.EditorInfo; + +@SuppressLint("InlinedApi") +@SuppressWarnings("unused") +public class LatinKeyboard extends Keyboard { + + private Key mEnterKey; + + public LatinKeyboard(Context context, int xmlLayoutResId) { + super(context, xmlLayoutResId); + } + + public LatinKeyboard(Context context, int layoutTemplateResId, + CharSequence characters, int columns, int horizontalPadding) { + super(context, layoutTemplateResId, characters, columns, horizontalPadding); + } + + @Override + protected Key createKeyFromXml(Resources res, Row parent, int x, int y, + XmlResourceParser parser) { + Key key = new LatinKey(res, parent, x, y, parser); + if (key.codes[0] == 10) { + mEnterKey = key; + } + return key; + } + + /** + * This looks at the ime options given by the current editor, to set the + * appropriate label on the keyboard's enter key (if it has one). + */ + void setImeOptions(Resources res, int options) { + if (mEnterKey == null) { + return; + } + + switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) { + case EditorInfo.IME_ACTION_GO: + mEnterKey.iconPreview = null; + mEnterKey.icon = null; + mEnterKey.label = res.getText(R.string.label_go_key); + break; + case EditorInfo.IME_ACTION_NEXT: + mEnterKey.iconPreview = null; + mEnterKey.icon = null; + mEnterKey.label = res.getText(R.string.label_next_key); + break; + case EditorInfo.IME_ACTION_SEARCH: + mEnterKey.icon = res.getDrawable( + R.drawable.sym_keyboard_search); + mEnterKey.label = null; + break; + case EditorInfo.IME_ACTION_SEND: + mEnterKey.iconPreview = null; + mEnterKey.icon = null; + mEnterKey.label = res.getText(R.string.label_send_key); + break; + default: + mEnterKey.icon = res.getDrawable( + R.drawable.sym_keyboard_return); + mEnterKey.label = null; + break; + } + } + + static class LatinKey extends Keyboard.Key { + + public LatinKey(Resources res, Keyboard.Row parent, int x, int y, XmlResourceParser parser) { + super(res, parent, x, y, parser); + } + + /** + * Overriding this method so that we can reduce the target area for the key that + * closes the keyboard. + */ + @Override + public boolean isInside(int x, int y) { + return super.isInside(x, codes[0] == KEYCODE_CANCEL ? y - 10 : y); + } + } + +} diff --git a/src/com/halbae87/koreanbasicime/LatinKeyboardView.java b/src/com/halbae87/koreanbasicime/LatinKeyboardView.java new file mode 100644 index 0000000..77a7c0e --- /dev/null +++ b/src/com/halbae87/koreanbasicime/LatinKeyboardView.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/* + * halbae87: this project is created from Soft Keyboard Sample source + */ + +package com.halbae87.koreanbasicime; + +import android.content.Context; +// import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.inputmethodservice.Keyboard.Key; +import android.util.AttributeSet; + +public class LatinKeyboardView extends KeyboardView { + + static final int KEYCODE_OPTIONS = -100; + + public LatinKeyboardView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected boolean onLongPress(Key key) { + /* + if (key.codes[0] == Keyboard.KEYCODE_ALT) { + getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null); + return true; + } else { + return super.onLongPress(key); + } + */ + return super.onLongPress(key); + } +} diff --git a/src/com/halbae87/koreanbasicime/SoftKeyboard.java b/src/com/halbae87/koreanbasicime/SoftKeyboard.java new file mode 100644 index 0000000..7e58840 --- /dev/null +++ b/src/com/halbae87/koreanbasicime/SoftKeyboard.java @@ -0,0 +1,1099 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/* + * halbae87: this project is created from Soft Keyboard Sample source + */ + +package com.halbae87.koreanbasicime; + +import android.annotation.SuppressLint; +import android.graphics.Rect; +import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.Keyboard; +import android.inputmethodservice.KeyboardView; +import android.text.method.MetaKeyKeyListener; +import android.util.Log; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; + +import java.util.ArrayList; +import java.util.List; + + +/* + just for reference + Hangul/English: KEYCODE_KANA (218) + Hanja: KEYCODE_EISU (212) + Win (Left): KEYCODE_META_LEFT (117) + Menu: KEYCODE_MENU (82) + sys. req./prtscr: KEYCODE_SYSRQ (120) + */ +/** + * Example of writing an input method for a soft keyboard. This code is + * focused on simplicity over completeness, so it should in no way be considered + * to be a complete soft keyboard implementation. Its purpose is to provide + * a basic example for how you would get started writing an input method, to + * be fleshed out as appropriate. + */ + + + +@SuppressLint("InlinedApi") +@SuppressWarnings("unused") +public class SoftKeyboard extends InputMethodService + implements KeyboardView.OnKeyboardActionListener { + static final boolean DEBUG = false; + + /** + * This boolean indicates the optional example code for performing + * processing of hard keys in addition to regular text generation + * from on-screen interaction. It would be used for input methods that + * perform language translations (such as converting text entered on + * a QWERTY keyboard to Chinese), but may not be used for input methods + * that are primarily intended to be used for on-screen text entry. + */ + static final boolean PROCESS_HARD_KEYS = true; + private final String TAG = "SoftKeyboard"; + + private KeyboardView mInputView; + // no suggestion for Korean + // private CandidateView mCandidateView; + // private CompletionInfo[] mCompletions; + + private StringBuilder mComposing = new StringBuilder(); + // private boolean mPredictionOn; + // private boolean mCompletionOn; + private int mLastDisplayWidth; + private boolean mCapsLock; + private long mLastShiftTime; + private long mMetaState; + private boolean mHwShift = false; + + private LatinKeyboard mSymbolsKeyboard; + private LatinKeyboard mSymbolsShiftedKeyboard; + private LatinKeyboard mQwertyKeyboard; + + // special key definitions. + static final int KEYCODE_HANGUL = 218; // KeyEvent.KEYCODE_KANA is available from API 16 + static final int KEYCODE_HANJA = 212; // KeyEvent.KEYCODE_EISU is available from API 16 + static final int KEYCODE_WIN_LEFT = 117; // KeyEvent.KEYCODE_META_LEFT is available from API 11 + static final int KEYCODE_SYSREQ = 120; // KeyEvent.KEYCODE_SYSREQ is available from API 11 + + private LatinKeyboard mKoreanKeyboard; + private LatinKeyboard mKoreanShiftedKeyboard; + private LatinKeyboard mBackupKeyboard; + + private LatinKeyboard mCurKeyboard; + + private String mWordSeparators; + + private KoreanAutomata kauto; + private boolean mNoKorean = false; + + /** + * Main initialization of the input method component. Be sure to call + * to super class. + */ + + @Override public void onCreate() { + super.onCreate(); + mWordSeparators = getResources().getString(R.string.word_separators); + + // enable for debug purpose only. otherwise, it will stuck here. + // android.os.Debug.waitForDebugger(); + } + + /** + * This is the point where you can do all of your UI initialization. It + * is called after creation and any configuration change. + */ + @Override public void onInitializeInterface() { + if (mQwertyKeyboard != null) { + // Configuration changes can happen after the keyboard gets recreated, + // so we need to be able to re-build the keyboards if the available + // space has changed. + int displayWidth = getMaxWidth(); + if (displayWidth == mLastDisplayWidth) return; + mLastDisplayWidth = displayWidth; + } + kauto = new KoreanAutomata(); + mQwertyKeyboard = new LatinKeyboard(this, R.xml.qwerty); + mSymbolsKeyboard = new LatinKeyboard(this, R.xml.symbols); + mSymbolsShiftedKeyboard = new LatinKeyboard(this, R.xml.symbols_shift); + mKoreanKeyboard = new LatinKeyboard(this, R.xml.korean); + mKoreanShiftedKeyboard = new LatinKeyboard(this, R.xml.korean_shifted); + mBackupKeyboard = null; + } + + /** + * Called by the framework when your view for creating input needs to + * be generated. This will be called the first time your input method + * is displayed, and every time it needs to be re-created such as due to + * a configuration change. + */ + @Override public View onCreateInputView() { + // Log.v(TAG,"onCreateInputView ---- enter"); + mInputView = (KeyboardView) getLayoutInflater().inflate( + R.layout.input, null); + mInputView.setOnKeyboardActionListener(this); + mInputView.setKeyboard(mQwertyKeyboard); + // Log.v(TAG,"onCreateInputView ---- leave"); + return mInputView; + } + + /** + * Called by the framework when your view for showing candidates needs to + * be generated, like {@link #onCreateInputView}. + */ + /* no candidate window for Korean + @Override public View onCreateCandidatesView() { + mCandidateView = new CandidateView(this); + mCandidateView.setService(this); + return mCandidateView; + } + */ + + // add this. no need full screen IME mode for this IME. ---- KGS need to check... + + @Override + public void onUpdateExtractingVisibility(EditorInfo ei) { + ei.imeOptions |= EditorInfo.IME_FLAG_NO_EXTRACT_UI; + super.onUpdateExtractingVisibility(ei); + } + + + /** + * This is the main point where we do our initialization of the input method + * to begin operating on an application. At this point we have been + * bound to the client, and are now receiving all of the detailed information + * about the target of our edits. + */ + @Override public void onStartInput(EditorInfo attribute, boolean restarting) { + super.onStartInput(attribute, restarting); + // Log.v(TAG,"onStartInput ---- enter"); + + // Reset our state. We want to do this even if restarting, because + // the underlying state of the text editor could have changed in any way. + mComposing.setLength(0); + // updateCandidates(); + + if (!restarting) { + // Clear shift states. + mMetaState = 0; + } + + // mPredictionOn = false; + // mCompletionOn = false; + // mCompletions = null; + kauto.FinishAutomataWithoutInput(); + + // We are now going to initialize our state based on the type of + // text being edited. + switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) { + case EditorInfo.TYPE_CLASS_NUMBER: + case EditorInfo.TYPE_CLASS_DATETIME: + // Numbers and dates default to the symbols keyboard, with + // no extra features. + mCurKeyboard = mSymbolsKeyboard; + mNoKorean = true; + if (kauto.IsKoreanMode()) + kauto.ToggleMode(); + break; + + case EditorInfo.TYPE_CLASS_PHONE: + // Phones will also default to the symbols keyboard, though + // often you will want to have a dedicated phone keyboard. + mCurKeyboard = mSymbolsKeyboard; + mNoKorean = true; + if (kauto.IsKoreanMode()) + kauto.ToggleMode(); + break; + + case EditorInfo.TYPE_CLASS_TEXT: + // This is general text editing. We will default to the + // normal alphabetic keyboard, and assume that we should + // be doing predictive text (showing candidates as the + // user types). + + // mPredictionOn = false; + + // We now look for a few special variations of text that will + // modify our behavior. + int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; + if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD || + variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { + // Do not display predictions / what the user is typing + // when they are entering a password. + mNoKorean = true; + // mPredictionOn = false; + } + + if (mNoKorean || !kauto.IsKoreanMode()) + mCurKeyboard = mQwertyKeyboard; + else + mCurKeyboard = mKoreanKeyboard; + //if (kauto.IsKoreanMode()) + // kauto.ToggleMode(); + + /* KGS, do I need these? + if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + || variation == EditorInfo.TYPE_TEXT_VARIATION_URI + || variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) { + // Our predictions are not useful for e-mail addresses + // or URIs. + mPredictionOn = false; + } + + if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { + // If this is an auto-complete text view, then our predictions + // will not be shown and instead we will allow the editor + // to supply their own. We only show the editor's + // candidates when in fullscreen mode, otherwise relying + // own it displaying its own UI. + mPredictionOn = false; + mCompletionOn = isFullscreenMode(); + } + */ + + // We also want to look at the current state of the editor + // to decide whether our alphabetic keyboard should start out + // shifted. + updateShiftKeyState(attribute); + break; + + default: + // For all unknown input types, default to the alphabetic + // keyboard with no special features. + // mCurKeyboard = mQwertyKeyboard; + + if (kauto.IsKoreanMode()) + mCurKeyboard = mKoreanKeyboard; + else + mCurKeyboard = mQwertyKeyboard; + updateShiftKeyState(attribute); + } + + // Update the label on the enter key, depending on what the application + // says it will do. + mCurKeyboard.setImeOptions(getResources(), attribute.imeOptions); + // Log.v(TAG,"onStartInput ---- leave"); +} + + @Override public void onFinishInputView(boolean finishingInput) { + super.onFinishInputView(finishingInput); + // Log.v(TAG,"onFinishInputView ---- enter"); + if (kauto.IsKoreanMode()) + kauto.FinishAutomataWithoutInput(); + mKoreanKeyboard.setShifted(false); + // Log.v(TAG,"onFinishInputView ---- leave"); + } + + /** + * This is called when the user is done editing a field. We can use + * this to reset our state. + */ + + @Override public void onFinishInput() { + super.onFinishInput(); + // Log.v(TAG,"onFinishInput ---- enter"); + + // Clear current composing text and candidates. + kauto.FinishAutomataWithoutInput(); + mNoKorean = false; + /* + InputConnection ic = getCurrentInputConnection(); + if (ic != null) + commitTyped(ic); + */ + mComposing.setLength(0); + // updateCandidates(); + + // We only hide the candidates window when finishing input on + // a particular editor, to avoid popping the underlying application + // up and down if the user is entering text into the bottom of + // its window. + setCandidatesViewShown(false); + + mCurKeyboard = mQwertyKeyboard; + if (mInputView != null) { + // let's reset Korean input mode in soft keyboard mode. H/W keyboard will not be affected with this. + // if (kauto.IsKoreanMode()) + // kauto.ToggleMode(); + mInputView.closing(); + } + // Log.v(TAG,"onFinishInput ---- leave"); + } + + @Override public void onStartInputView(EditorInfo attribute, boolean restarting) { + super.onStartInputView(attribute, restarting); + // Log.v(TAG,"onStartInputView ---- enter, restarting = " + restarting); + // Apply the selected keyboard to the input view. + if (kauto.IsKoreanMode()) + mCurKeyboard = mKoreanKeyboard; + else + mCurKeyboard = mQwertyKeyboard; + mInputView.setKeyboard(mCurKeyboard); + mInputView.closing(); + // Log.v(TAG,"onStartInputView ---- leave"); + } + + /** + * Deal with the editor reporting movement of its cursor. + */ + @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd, + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd) { + super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, + candidatesStart, candidatesEnd); + + // If the current selection in the text view changes, we should + // clear whatever candidate text we have. + if (mComposing.length() > 0 && (newSelStart != candidatesEnd + || newSelEnd != candidatesEnd)) { + mComposing.setLength(0); + // Log.v(TAG, "onUpdateSelection --- called"); + kauto.FinishAutomataWithoutInput(); + // updateCandidates(); + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + ic.finishComposingText(); + } + } + } + + /** + * This tells us about completions that the editor has determined based + * on the current text in it. We want to use this in fullscreen mode + * to show the completions ourself, since the editor can not be seen + * in that situation. + */ + /* do nothing + @Override public void onDisplayCompletions(CompletionInfo[] completions) { + if (mCompletionOn) { + mCompletions = completions; + if (completions == null) { + setSuggestions(null, false, false); + return; + } + + List stringList = new ArrayList(); + for (int i=0; i<(completions != null ? completions.length : 0); i++) { + CompletionInfo ci = completions[i]; + if (ci != null) stringList.add(ci.getText().toString()); + } + setSuggestions(stringList, true, true); + } + } + */ + /** + * This translates incoming hard key events in to edit operations on an + * InputConnection. It is only needed when using the + * PROCESS_HARD_KEYS option. + */ + private boolean translateKeyDown(int keyCode, KeyEvent event) { + // // Log.v(TAG, "translateKeyDown() called"); + mMetaState = MetaKeyKeyListener.handleKeyDown(mMetaState, + keyCode, event); + int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(mMetaState)); + mMetaState = MetaKeyKeyListener.adjustMetaAfterKeypress(mMetaState); + InputConnection ic = getCurrentInputConnection(); + if (c == 0 || ic == null) { + return false; + } + + boolean dead = false; + + if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) { + dead = true; + c = c & KeyCharacterMap.COMBINING_ACCENT_MASK; + } + + if (mComposing.length() > 0) { + char accent = mComposing.charAt(mComposing.length() -1 ); + int composed = KeyEvent.getDeadChar(accent, c); + + if (composed != 0) { + c = composed; + mComposing.setLength(mComposing.length()-1); + } + } + + onKey(c, null); + + return true; + } + + /** + * Use this to monitor key events being delivered to the application. + * We get first crack at them, and can either resume them or let them + * continue to the app. + */ + @SuppressWarnings({ }) + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { + // // Log.v(TAG, "onKeyDown - keyCode = " + event.keyCodeToString(keyCode) + " ("+ keyCode +")"); + if (event.isShiftPressed()) + mHwShift = true; + // if ALT or CTRL meta keys are using, the key event should not be touched here and be passed through to. + if ((event.getMetaState() & (KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK)) == 0) + { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + // The InputMethodService already takes care of the back + // key for us, to dismiss the input method if it is shown. + // However, our keyboard could be showing a pop-up window + // that back should dismiss, so we first allow it to do that. + if (event.getRepeatCount() == 0 && mInputView != null) { + if (mInputView.handleBack()) { + return true; + } + } + break; + case KeyEvent.KEYCODE_SPACE: + if ( /* (event.getFlags() & event.FLAG_SOFT_KEYBOARD) != 0 && */!mNoKorean && event.isShiftPressed()) + { + if (mComposing.length() > 0) + { + InputConnection ic = getCurrentInputConnection(); + if (ic != null) + commitTyped(ic); + } + if (kauto.IsKoreanMode()) + { + kauto.FinishAutomataWithoutInput(); + } + kauto.ToggleMode(); + // // Log.v(TAG, "onKeyDown -- SHIFT SPACE. Korean Mode = " + kauto.IsKoreanMode()); + return true; + } + else + { + translateKeyDown(keyCode, event); + return true; + } + // break; + + // for Korean Keyboard only. + case KEYCODE_HANGUL: + if (!mNoKorean) + { + // // Log.v(TAG, "onKeyDown -- KEYCODE_HANGUL. Korean Mode = " + kauto.IsKoreanMode() +" Let's toggle it."); + if (mComposing.length() > 0) + { + InputConnection ic = getCurrentInputConnection(); + if (ic != null) + commitTyped(ic); + } + if (kauto.IsKoreanMode()) + { + kauto.FinishAutomataWithoutInput(); + } + kauto.ToggleMode(); + // consume this event. + return true; + } + else + { + // // Log.v(TAG, "onKeyDown -- KEYCODE_HANGUL. but Korean is not allowed in this context. ignore this."); + } + break; + + case KeyEvent.KEYCODE_DEL: + // Special handling of the delete key: if we currently are + // composing text for the user, we want to modify that instead + // of let the application to the delete itself. + if (mComposing.length() > 0) { + onKey(Keyboard.KEYCODE_DELETE, null); + return true; + } + break; + + case KeyEvent.KEYCODE_ENTER: + // Let the underlying text editor always handle these. + if (kauto.IsKoreanMode()) + { + kauto.FinishAutomataWithoutInput(); + } + return false; + + default: + // For all other keys, if we want to do transformations on + // text being entered with a hard keyboard, we need to process + // it and do the appropriate action. + if (PROCESS_HARD_KEYS) { + if (keyCode == KeyEvent.KEYCODE_SPACE + && (event.getMetaState()&KeyEvent.META_ALT_ON) != 0) { + // A silly example: in our input method, Alt+Space + // is a shortcut for 'android' in lower case. + InputConnection ic = getCurrentInputConnection(); + if (ic != null) { + // First, tell the editor that it is no longer in the + // shift state, since we are consuming this. + ic.clearMetaKeyStates(KeyEvent.META_ALT_ON); + keyDownUp(KeyEvent.KEYCODE_A); + keyDownUp(KeyEvent.KEYCODE_N); + keyDownUp(KeyEvent.KEYCODE_D); + keyDownUp(KeyEvent.KEYCODE_R); + keyDownUp(KeyEvent.KEYCODE_O); + keyDownUp(KeyEvent.KEYCODE_I); + keyDownUp(KeyEvent.KEYCODE_D); + // And we consume this event. + return true; + } + } + if ( /* mPredictionOn && */ translateKeyDown(keyCode, event)) { + return true; + } + } + } + } + return super.onKeyDown(keyCode, event); + } + + /** + * Use this to monitor key events being delivered to the application. + * We get first crack at them, and can either resume them or let them + * continue to the app. + */ + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { + // If we want to do transformations on text being entered with a hard + // keyboard, we need to process the up events to update the meta key + // state we are tracking. + // // Log.v(TAG, "onKeyUp - keyCode = " + event.keyCodeToString(keyCode)); + if (PROCESS_HARD_KEYS) { + // if (mPredictionOn) { + mMetaState = MetaKeyKeyListener.handleKeyUp(mMetaState, + keyCode, event); + // } + } + mHwShift = false; + + return super.onKeyUp(keyCode, event); + } + + /** + * Helper function to commit any text being composed in to the editor. + */ + private void commitTyped(InputConnection inputConnection) { + // Log.v(TAG, "commitTyped ------- mComposing = [" + mComposing +"] length = " + mComposing.length()); + if (mComposing.length() > 0) { + inputConnection.commitText(mComposing, 1); + mComposing.setLength(0); + // updateCandidates(); + } + } + + /** + * Helper to update the shift state of our keyboard based on the initial + * editor state. + */ + // KGS need to fix.. + private void updateShiftKeyState(EditorInfo attr) { + if (attr != null && mInputView != null) + { + if (mQwertyKeyboard == mInputView.getKeyboard()) + { + int caps = 0; + EditorInfo ei = getCurrentInputEditorInfo(); + if (ei != null && ei.inputType != EditorInfo.TYPE_NULL) { + caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType); + } + // // Log.v(TAG, "updateShiftKeyState - mCapsLock = " + mCapsLock + ", caps = " + caps); + mInputView.setShifted(mCapsLock || caps != 0); + } + else if (mKoreanShiftedKeyboard == mInputView.getKeyboard()) + { + mKoreanShiftedKeyboard.setShifted(false); + mInputView.setKeyboard(mKoreanKeyboard); + mKoreanKeyboard.setShifted(false); + } + } + } + + /** + * Helper to determine if a given character code is alphabetic. + */ + private boolean isAlphabet(int code) { + if (Character.isLetter(code)) { + return true; + } else { + return false; + } + } + + /** + * Helper to send a key down / key up pair to the current editor. + */ + private void keyDownUp(int keyEventCode) { + getCurrentInputConnection().sendKeyEvent( + new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode)); + getCurrentInputConnection().sendKeyEvent( + new KeyEvent(KeyEvent.ACTION_UP, keyEventCode)); + } + + /** + * Helper to send a character to the editor as raw key events. + */ + private void sendKey(int keyCode) { + // Log.v(TAG, "sendKey ------- keyCode = " + keyCode); + switch (keyCode) { + case '\n': + keyDownUp(KeyEvent.KEYCODE_ENTER); + // Log.v(TAG, "sendKey ------- ENTER"); + break; + default: + if (keyCode >= '0' && keyCode <= '9') { + keyDownUp(keyCode - '0' + KeyEvent.KEYCODE_0); + // Log.v(TAG, "sendKey ------- DIGIT [" + (keyCode - '0') + "]"); + } else { + getCurrentInputConnection().commitText(String.valueOf((char) keyCode), 1); + // Log.v(TAG, "sendKey ------- maybe white space. valueOf [" + (String.valueOf((char) keyCode)) + "]"); + } + break; + } + } + + // Implementation of KeyboardViewListener + + public void onKey(int primaryCode, int[] keyCodes) { + // Log.v(TAG, "onKey - primaryCode = " + primaryCode); + if (isWordSeparator(primaryCode)) { + // Log.v(TAG, " -- separator = [" + (char) primaryCode + "]"); + // Handle separator + if (mComposing.length() > 0) { + commitTyped(getCurrentInputConnection()); + } + if (kauto.IsKoreanMode()) + kauto.FinishAutomataWithoutInput(); + sendKey(primaryCode); + if (mInputView != null) + updateShiftKeyState(getCurrentInputEditorInfo()); + } else if (primaryCode == Keyboard.KEYCODE_DELETE) { + handleBackspace(); + } else if (primaryCode == Keyboard.KEYCODE_SHIFT) { + handleShift(); + } else if (primaryCode == Keyboard.KEYCODE_CANCEL) { + handleClose(); + return; + } else if (primaryCode == Keyboard.KEYCODE_ALT) { + // Show a menu or somethin' + // Log.v(TAG,"onKey ---------- KEYCODE_ALT. let's use it as input mode change"); + if (mInputView != null) + { + if (!mNoKorean) + { + Keyboard current; + if (kauto.IsKoreanMode()) + { + // Log.v(TAG, " change keyboard view to English"); + current = mQwertyKeyboard; + } + else + { + // Log.v(TAG, " change keyboard view to Korean"); + current = mKoreanKeyboard; + } + mInputView.setKeyboard(current); + if (mComposing.length() > 0) + { + commitTyped(getCurrentInputConnection()); + } + kauto.ToggleMode(); + if (current == mQwertyKeyboard || current == mKoreanKeyboard) + { + current.setShifted(false); + } + } + else + { + // Log.v(TAG, " mNoKorean is ture. ignore the toggle key"); + } + } + } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE + && mInputView != null) { + Keyboard current = mInputView.getKeyboard(); + if (current == mSymbolsKeyboard || current == mSymbolsShiftedKeyboard) { + if (mBackupKeyboard != null) + current = mBackupKeyboard; + else + current = mQwertyKeyboard; + mBackupKeyboard = null; + // reset Korean input mode. + if (current == mQwertyKeyboard && kauto.IsKoreanMode()) + kauto.ToggleMode(); + } else { + mBackupKeyboard = (LatinKeyboard) current; + current = mSymbolsKeyboard; + // need to submit current composition string + if (mComposing.length() > 0) + { + commitTyped(getCurrentInputConnection()); + } + if (kauto.IsKoreanMode()) + kauto.FinishAutomataWithoutInput(); + } + mInputView.setKeyboard(current); + if (current == mSymbolsKeyboard) { + current.setShifted(false); + } + } else { + handleCharacter(primaryCode, keyCodes); + } + } + + public void onText(CharSequence text) { + InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; + ic.beginBatchEdit(); + if (mComposing.length() > 0) { + commitTyped(ic); + } + ic.commitText(text, 0); + ic.endBatchEdit(); + updateShiftKeyState(getCurrentInputEditorInfo()); + } + + /** + * Update the list of available candidates from the current composing + * text. This will need to be filled in by however you are determining + * candidates. + */ + /* no candidate window + private void updateCandidates() { + if (!mCompletionOn) { + if (mComposing.length() > 0) { + ArrayList list = new ArrayList(); + list.add(mComposing.toString()); + setSuggestions(list, true, true); + } else { + setSuggestions(null, false, false); + } + } + } + + public void setSuggestions(List suggestions, boolean completions, + boolean typedWordValid) { + if (suggestions != null && suggestions.size() > 0) { + setCandidatesViewShown(true); + } else if (isExtractViewShown()) { + setCandidatesViewShown(true); + } + if (mCandidateView != null) { + mCandidateView.setSuggestions(suggestions, completions, typedWordValid); + } + } + */ + private void handleBackspace() { + // boolean cont = false; + if (kauto.IsKoreanMode()) + { + int ret = kauto.DoBackSpace(); + if (ret == KoreanAutomata.ACTION_ERROR) + { + // // Log.v(TAG, "handleBackspace() - calling DoBackSpace() failed."); + //// Log.v(TAG, " mCompositionString = [" + kauto.GetCompositionString()+"]"); + // // Log.v(TAG, " mState = " + kauto.GetState()); + updateShiftKeyState(getCurrentInputEditorInfo()); + return; + } + if ((ret & KoreanAutomata.ACTION_UPDATE_COMPOSITIONSTR) != 0) + { + if (kauto.GetCompositionString() != "") + { + // mComposing.setLength(0); + if (mComposing.length() > 0) + { + mComposing.replace(mComposing.length() -1, mComposing.length(), kauto.GetCompositionString()); + getCurrentInputConnection().setComposingText(mComposing, 1); + } + // mComposing.append(kauto.GetCompositionString()); + + updateShiftKeyState(getCurrentInputEditorInfo()); + return; + } + /* + else + { + cont = true; + // mComposing.setLength(0); + // getCurrentInputConnection().commitText("", 0); + } + */ + } + /* + if ((ret & KoreanAutomata.ACTION_USE_INPUT_AS_RESULT) != 0) + { + // // Log.v(TAG, "handleBackspace() - something unexpected behavior happened during DoBackSpace().."); + // // Log.v(TAG, " - Let it go through."); + cont = true; + } + if (!cont) + return; + */ + // otherwise, just leave it. + } + final int length = mComposing.length(); + if (length > 1) { + mComposing.delete(length - 1, length); + getCurrentInputConnection().setComposingText(mComposing, 1); + // updateCandidates(); + } else if (length > 0) { + mComposing.setLength(0); + getCurrentInputConnection().commitText("", 0); + // updateCandidates(); + } else { + keyDownUp(KeyEvent.KEYCODE_DEL); + } + updateShiftKeyState(getCurrentInputEditorInfo()); + } + + private void handleShift() { + if (mInputView == null) { + return; + } + + Keyboard currentKeyboard = mInputView.getKeyboard(); + if (mQwertyKeyboard == currentKeyboard) { + // Alphabet keyboard + checkToggleCapsLock(); + mInputView.setShifted(mCapsLock || !mInputView.isShifted()); + } + // add Korean Keyboards + else if (currentKeyboard == mKoreanKeyboard) + { + // // Log.v(TAG,"handleShift -- shift on Korean Keyboard"); + mKoreanKeyboard.setShifted(true); + mInputView.setKeyboard(mKoreanShiftedKeyboard); + mKoreanShiftedKeyboard.setShifted(true); + } + else if (currentKeyboard == mKoreanShiftedKeyboard) + { + // // Log.v(TAG,"handleShift -- shift off Korean Keyboard"); + mKoreanShiftedKeyboard.setShifted(false); + mInputView.setKeyboard(mKoreanKeyboard); + mKoreanKeyboard.setShifted(false); + } + // end of Korean keyboard care.. + else if (currentKeyboard == mSymbolsKeyboard) { + mSymbolsKeyboard.setShifted(true); + mInputView.setKeyboard(mSymbolsShiftedKeyboard); + mSymbolsShiftedKeyboard.setShifted(true); + } else if (currentKeyboard == mSymbolsShiftedKeyboard) { + mSymbolsShiftedKeyboard.setShifted(false); + mInputView.setKeyboard(mSymbolsKeyboard); + mSymbolsKeyboard.setShifted(false); + } + } + + private void handleCharacter(int primaryCode, int[] keyCodes) { + int keyState = InputTables.KEYSTATE_NONE; + + if (isInputViewShown()) { + if (mInputView.isShifted()) { + primaryCode = Character.toUpperCase(primaryCode); + keyState |= InputTables.KEYSTATE_SHIFT; + } + } + // for h/w keyboard.... + if (mHwShift) + keyState |= InputTables.KEYSTATE_SHIFT; + // Log.v(TAG, "handleCharacter() - POS 1"); + +// if (isAlphabet(primaryCode) && mPredictionOn ) { + if (isAlphabet(primaryCode)) { + int ret = kauto.DoAutomata((char )primaryCode, keyState); + if (ret < 0) + { + // Log.v(TAG,"handleCharacter() - DoAutomata() call failed. primaryCode = " + primaryCode + " keyStete = " + keyState); + if (kauto.IsKoreanMode()) + kauto.ToggleMode(); + } + else + { + // debug block.. + // Log.v(TAG, "handleCharacter - After calling DoAutomata()"); + // Log.v(TAG, " KoreanMode = [" + (kauto.IsKoreanMode()? "true" : "false") + "]"); + // Log.v(TAG, " CompleteString = [" + kauto.GetCompleteString() + "]"); + // Log.v(TAG, " CompositionString = [" + kauto.GetCompositionString() + "]"); + // Log.v(TAG, " State = [" + kauto.GetState() + "]"); + // Log.v(TAG, " ret = [" + ret + "]"); + + if ((ret & KoreanAutomata.ACTION_UPDATE_COMPLETESTR) != 0) + { + // Log.v(TAG, "--- UPDATE_COMPLETESTR"); + // mComposing.setLength(0); + if (mComposing.length() > 0) + mComposing.replace(mComposing.length()-1, mComposing.length(), kauto.GetCompleteString()); + else + mComposing.append(kauto.GetCompleteString()); + if (mComposing.length() > 0) { + getCurrentInputConnection().setComposingText(mComposing, 1); + // commitTyped(getCurrentInputConnection()); + } + } + if ((ret & KoreanAutomata.ACTION_UPDATE_COMPOSITIONSTR) != 0) + { + // Log.v(TAG, "--- UPDATE_COMPOSITIONSTR"); + // mComposing.setLength(0); + if ((mComposing.length() > 0) && ((ret & KoreanAutomata.ACTION_UPDATE_COMPLETESTR) == 0)) + mComposing.replace(mComposing.length()-1, mComposing.length(), kauto.GetCompositionString()); + else + mComposing.append(kauto.GetCompositionString()); + getCurrentInputConnection().setComposingText(mComposing, 1); + } + } + if ((ret & KoreanAutomata.ACTION_USE_INPUT_AS_RESULT) != 0) + { + // Log.v(TAG, "--- USE_INPUT_AS_RESULT"); + mComposing.append((char) primaryCode); + getCurrentInputConnection().setComposingText(mComposing, 1); + } + updateShiftKeyState(getCurrentInputEditorInfo()); + // updateCandidates(); + } else { + // Log.v(TAG, "handleCharacter() - POS 2"); + int ret = kauto.DoAutomata((char )primaryCode, keyState); + if (ret < 0) + { + // Log.v(TAG,"handleCharacter() - DoAutomata() call failed. primaryCode = " + primaryCode + " keyStete = " + keyState); + if (kauto.IsKoreanMode()) + kauto.ToggleMode(); + } + else + { + // Log.v(TAG, "handleCharacter - After calling DoAutomata()"); + // Log.v(TAG, " KoreanMode = [" + (kauto.IsKoreanMode()? "true" : "false") + "]"); + // Log.v(TAG, " CompleteString = [" + kauto.GetCompleteString() + "]"); + // Log.v(TAG, " CompositionString = [" + kauto.GetCompositionString() + "]"); + // Log.v(TAG, " State = [" + kauto.GetState() + "]"); + // Log.v(TAG, " ret = [" + ret + "]"); + if ((ret & KoreanAutomata.ACTION_UPDATE_COMPLETESTR) != 0) + { + // // Log.v(TAG, " update complete string - " + kauto.GetCompleteString()); + // mComposing.setLength(0); + if (mComposing.length() > 0) + mComposing.replace(mComposing.length()-1, mComposing.length(), kauto.GetCompleteString()); + else + mComposing.append(kauto.GetCompleteString()); + // mComposing.append(kauto.GetCompleteString()); + if (mComposing.length() > 0) { + getCurrentInputConnection().setComposingText(mComposing, 1); + // commitTyped(getCurrentInputConnection()); + } + } + if ((ret & KoreanAutomata.ACTION_UPDATE_COMPOSITIONSTR) != 0) + { + // // Log.v(TAG, " update composition string - " + kauto.GetCompositionString()); + // mComposing.setLength(0); + if ((mComposing.length() > 0) && ((ret & KoreanAutomata.ACTION_UPDATE_COMPLETESTR) == 0)) + mComposing.replace(mComposing.length()-1, mComposing.length(), kauto.GetCompositionString()); + else + mComposing.append(kauto.GetCompositionString()); + // mComposing.append(kauto.GetCompositionString()); + if (mComposing.length() > 0) { + getCurrentInputConnection().setComposingText(mComposing, 1); + } + } + } + if ((ret & KoreanAutomata.ACTION_USE_INPUT_AS_RESULT) != 0) + { + getCurrentInputConnection().commitText( + String.valueOf((char) primaryCode), 1); + } + // updateShiftKeyState(getCurrentInputEditorInfo()); +// getCurrentInputConnection().commitText( +// String.valueOf((char) primaryCode), 1); + } + } + + private void handleClose() { + commitTyped(getCurrentInputConnection()); + requestHideSelf(0); + mInputView.closing(); + } + + private void checkToggleCapsLock() { + long now = System.currentTimeMillis(); + if (mLastShiftTime + 800 > now) { + mCapsLock = !mCapsLock; + mLastShiftTime = 0; + } else { + mLastShiftTime = now; + } + } + + private String getWordSeparators() { + return mWordSeparators; + } + + public boolean isWordSeparator(int code) { + String separators = getWordSeparators(); + return separators.contains(String.valueOf((char)code)); + } + + /* no candidate view for Korean. at least, not now. + public void pickDefaultCandidate() { + pickSuggestionManually(0); + } + + public void pickSuggestionManually(int index) { + if (mCompletionOn && mCompletions != null && index >= 0 + && index < mCompletions.length) { + CompletionInfo ci = mCompletions[index]; + getCurrentInputConnection().commitCompletion(ci); + if (mCandidateView != null) { + mCandidateView.clear(); + } + updateShiftKeyState(getCurrentInputEditorInfo()); + } else if (mComposing.length() > 0) { + // If we were generating candidate suggestions for the current + // text, we would commit one of them here. But for this sample, + // we will just commit the current text. + commitTyped(getCurrentInputConnection()); + } + } + */ + public void pickSuggestionManually(int index) {} // just fake it to build. + + + public void swipeRight() { +// if (mCompletionOn) { +// pickDefaultCandidate(); +// } + } + + public void swipeLeft() { + handleBackspace(); + } + + public void swipeDown() { + handleClose(); + } + + public void swipeUp() { + } + + public void onPress(int primaryCode) { + } + + public void onRelease(int primaryCode) { + } +}