diff options
author | 2013-04-24 17:43:26 +0900 | |
---|---|---|
committer | 2013-04-25 14:55:07 +0900 | |
commit | a79ba8a3d6dbdee777f156449c436fd3a4a57feb (patch) | |
tree | 5a6620e3af2d4392ca0e8d5b9e91476c67c81c39 | |
parent | 3ee39be1bd21996cea54c8a3b43cefc937e5c69d (diff) | |
download | latinime-a79ba8a3d6dbdee777f156449c436fd3a4a57feb.tar.gz latinime-a79ba8a3d6dbdee777f156449c436fd3a4a57feb.tar.xz latinime-a79ba8a3d6dbdee777f156449c436fd3a4a57feb.zip |
Implement a functionality to add an entry to the user dictionary
Bug: 8600958
Change-Id: Ic472500406b9d54ec4052c490ee7cef62fc4e52a
15 files changed, 1242 insertions, 5 deletions
diff --git a/java/res/drawable-hdpi/ic_menu_add.png b/java/res/drawable-hdpi/ic_menu_add.png Binary files differnew file mode 100644 index 000000000..4b68f52ad --- /dev/null +++ b/java/res/drawable-hdpi/ic_menu_add.png diff --git a/java/res/drawable-mdpi/ic_menu_add.png b/java/res/drawable-mdpi/ic_menu_add.png Binary files differnew file mode 100644 index 000000000..15ffadd36 --- /dev/null +++ b/java/res/drawable-mdpi/ic_menu_add.png diff --git a/java/res/drawable-xhdpi/ic_menu_add.png b/java/res/drawable-xhdpi/ic_menu_add.png Binary files differnew file mode 100644 index 000000000..420510e93 --- /dev/null +++ b/java/res/drawable-xhdpi/ic_menu_add.png diff --git a/java/res/layout/user_dictionary_add_word.xml b/java/res/layout/user_dictionary_add_word.xml new file mode 100644 index 000000000..bbf9b1b5b --- /dev/null +++ b/java/res/layout/user_dictionary_add_word.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + 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. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/user_dict_settings_add_dialog_top" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <com.android.internal.widget.DialogTitle + style="?android:attr/windowTitleStyle" + android:layout_width="match_parent" + android:layout_height="64dip" + android:layout_marginEnd="16dip" + android:layout_marginStart="16dip" + android:ellipsize="end" + android:gravity="center_vertical|start" + android:singleLine="true" + android:text="@string/user_dict_settings_add_dialog_title" /> + + <View + android:layout_width="match_parent" + android:layout_height="2dip" + android:background="@android:color/holo_blue_light" /> + </LinearLayout> + + <EditText + android:id="@+id/user_dictionary_add_word_text" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="fill_horizontal|center_vertical" + android:layout_marginBottom="8dip" + android:layout_marginStart="8dip" + android:layout_marginTop="8dip" + android:hint="@string/user_dict_settings_add_word_hint" + android:imeOptions="flagNoFullscreen" + android:inputType="textNoSuggestions" + android:maxLength="@integer/user_dictionary_max_word_length" > + + <requestFocus /> + </EditText> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:divider="?android:attr/dividerHorizontal" + android:dividerPadding="0dip" + android:orientation="vertical" + android:showDividers="beginning" > + + <LinearLayout + style="?android:attr/buttonBarStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:measureWithLargestChild="true" + android:orientation="horizontal" > + + <Button + style="?android:attr/buttonBarButtonStyle" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:layout_weight="1" + android:maxLines="2" + android:onClick="onClickCancel" + android:text="@string/cancel" + android:textSize="14sp" /> + + <Button + style="?android:attr/buttonBarButtonStyle" + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:layout_weight="1" + android:maxLines="2" + android:onClick="onClickConfirm" + android:text="@string/user_dict_settings_add_dialog_confirm" + android:textSize="14sp" /> + </LinearLayout> + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/java/res/layout/user_dictionary_add_word_fullscreen.xml b/java/res/layout/user_dictionary_add_word_fullscreen.xml new file mode 100644 index 000000000..75e86c509 --- /dev/null +++ b/java/res/layout/user_dictionary_add_word_fullscreen.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + 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. + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/user_dict_settings_add_dialog_top" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" > + + <TextView + style="?android:attr/listSeparatorTextViewStyle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/user_dict_settings_add_screen_title" /> + + <EditText + android:id="@+id/user_dictionary_add_word_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="fill_horizontal|center_vertical" + android:layout_marginBottom="8dip" + android:layout_marginStart="8dip" + android:layout_marginTop="8dip" + android:hint="@string/user_dict_settings_add_word_hint" + android:imeOptions="flagNoFullscreen" + android:inputType="textNoSuggestions" + android:maxLength="@integer/user_dictionary_max_word_length" > + + <requestFocus /> + </EditText> + + <GridLayout + android:id="@+id/user_dictionary_add_word_grid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginEnd="8dip" + android:layout_marginStart="8dip" + android:columnCount="2" > + + <TextView + android:id="@+id/user_dictionary_add_shortcut_label" + style="?android:attr/textAppearanceSmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start|center_vertical" + android:text="@string/user_dict_settings_add_shortcut_option_name" /> + + <EditText + android:id="@+id/user_dictionary_add_shortcut" + android:layout_width="wrap_content" + android:layout_gravity="fill_horizontal|center_vertical" + android:layout_marginBottom="8dip" + android:layout_marginStart="8dip" + android:layout_marginTop="8dip" + android:hint="@string/user_dict_settings_add_shortcut_hint" + android:imeOptions="flagNoFullscreen" + android:inputType="textNoSuggestions" + android:maxLength="@integer/user_dictionary_max_word_length" /> + + <TextView + android:id="@+id/user_dictionary_add_locale_label" + style="?android:attr/textAppearanceSmall" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start|center_vertical" + android:text="@string/user_dict_settings_add_locale_option_name" + android:visibility="gone" /> + + <Spinner + android:id="@+id/user_dictionary_add_locale" + android:layout_width="wrap_content" + android:layout_gravity="fill_horizontal|center_vertical" + android:layout_marginBottom="8dip" + android:layout_marginStart="8dip" + android:layout_marginTop="8dip" + android:visibility="gone" /> + </GridLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/java/res/layout/user_dictionary_item.xml b/java/res/layout/user_dictionary_item.xml new file mode 100644 index 000000000..3062ed89a --- /dev/null +++ b/java/res/layout/user_dictionary_item.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:gravity="center_vertical" + android:paddingEnd="?android:attr/scrollbarSize" + android:background="?android:attr/selectableItemBackground" > + + <RelativeLayout android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="15dip" + android:layout_marginEnd="6dip" + android:layout_marginTop="6dip" + android:layout_marginBottom="6dip" + android:layout_weight="1"> + + <TextView android:id="@+android:id/text1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceMedium" + android:ellipsize="marquee" + android:fadingEdge="horizontal" /> + + <TextView android:id="@+android:id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@android:id/text1" + android:layout_alignStart="@android:id/text1" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textColor="?android:attr/textColorSecondary" + android:maxLines="1" /> + + </RelativeLayout> + +</LinearLayout> diff --git a/java/res/layout/user_dictionary_preference_list_fragment.xml b/java/res/layout/user_dictionary_preference_list_fragment.xml new file mode 100644 index 000000000..40e562c87 --- /dev/null +++ b/java/res/layout/user_dictionary_preference_list_fragment.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, The Android Open Source Project +** +** 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. +*/ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/transparent" + android:orientation="vertical" > + + <ListView + android:id="@android:id/list" + android:layout_width="match_parent" + android:layout_height="0px" + android:layout_weight="1" + android:cacheColorHint="@android:color/transparent" + android:clipToPadding="false" + android:drawSelectorOnTop="false" + android:paddingTop="0dip" + android:scrollbarAlwaysDrawVerticalTrack="true" /> + + <TextView + android:id="@android:id/empty" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:padding="5dip" + android:visibility="gone" /> + +</LinearLayout>
\ No newline at end of file diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml index e9b34aa12..da735cf5a 100644 --- a/java/res/values/dimens.xml +++ b/java/res/values/dimens.xml @@ -117,4 +117,6 @@ <!-- Inset used in Accessibility mode to avoid accidental key presses when a finger slides off the screen. --> <dimen name="accessibility_edge_slop">8dp</dimen> + + <integer name="user_dictionary_max_word_length" translatable="false">48</integer> </resources> diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml index d8a88a8eb..ff7942681 100644 --- a/java/res/values/strings.xml +++ b/java/res/values/strings.xml @@ -562,4 +562,76 @@ Tip: You can download and remove dictionaries by going to <b>Language & i <!-- Version text [CHAR LIMIT=30]--> <string name="version_text">Version <xliff:g id="version_number" example="1.0.1864.643521">%1$s</xliff:g></string> + + <!-- User dictionary settings --> + <!-- User dictionary settings, The titlebar text of the User dictionary settings screen. --> + <!-- This resource is corresponding to msgid="765659257455000490" --> + <string name="user_dict_settings_titlebar">User dictionary</string> + <!-- User dictionary settings, The title of the list item to go into the User dictionary settings screen when there is only one user dictionary. [CHAR LIMIT=35] --> + <!-- This resource is corresponding to msgid="524997218433540614" --> + <string name="user_dict_single_settings_title">Personal dictionary</string> + <!-- User dictionary settings, The title of the list item to go into the User dictionary list when there are several user dictionaries. [CHAR LIMIT=35] --> + <!-- This resource is corresponding to msgid="3735224433307996276" --> + <string name="user_dict_multiple_settings_title">Personal dictionaries</string> + <!-- User dictionary settings. The summary of the listem item to go into the User dictionary settings screen. --> + <string name="user_dict_settings_summary" translatable="false">""</string> + <!-- User dictionary settings. The title of the menu item to add a new word to the user dictionary. --> + <!-- This resource is corresponding to msgid="4056762757149923551" --> + <string name="user_dict_settings_add_menu_title">Add</string> + <!-- User dictionary settings. The title of the dialog to add a new word to the user dictionary. [CHAR LIMIT=25] --> + <!-- This resource is corresponding to msgid="4702613990174126482" --> + <string name="user_dict_settings_add_dialog_title">Add to dictionary</string> + <!-- User dictionary settings. The title of the screen to add/edit a new word to the user dictionary; it describes the phrase that will be added to the user dictionary. [CHAR LIMIT=25] --> + <!-- This resource is corresponding to msgid="742580720124344291" --> + <string name="user_dict_settings_add_screen_title">Phrase</string> + <!-- User dictionary settings. Text on the dialog button to pop more options for adding a word. [CHAR LIMIT=16] --> + <!-- This resource is corresponding to msgid="8848798370746019825" --> + <string name="user_dict_settings_add_dialog_more_options">More options</string> + <!-- User dictionary settings. Text on the dialog button mask advanced options. [CHAR LIMIT=15] --> + <!-- This resource is corresponding to msgid="2441785268726036101" --> + <string name="user_dict_settings_add_dialog_less_options">Less options</string> + <!-- User dictionary settings. Text on the dialog button to confirm adding a word. [CHAR LIMIT=15] --> + <!-- This resource is corresponding to msgid="6225823625332416144" --> + <string name="user_dict_settings_add_dialog_confirm">OK</string> + <!-- User dictionary settings. Label to put before the word field (that's the word that will actually be added to the user dictionary when OK is pressed). [CHAR LIMIT=20] --> + <!-- This resource is corresponding to msgid="7868879174905963135" --> + <string name="user_dict_settings_add_word_option_name">Word:</string> + <!-- User dictionary settings. Label to put before the shortcut field (once a shortcut is registered, the user can type the shortcut and get the word it points to in the suggestions). [CHAR LIMIT=20] --> + <!-- This resource is corresponding to msgid="660089258866063925" --> + <string name="user_dict_settings_add_shortcut_option_name">Shortcut:</string> + <!-- User dictionary settings. Label to put before the language field. [CHAR LIMIT=20] --> + <!-- This resource is corresponding to msgid="5696358317061318532" --> + <string name="user_dict_settings_add_locale_option_name">Language:</string> + <!-- User dictionary settings. Hint for the text field to type the word to add to the user dictionary. [CHAR LIMIT=35] --> + <!-- This resource is corresponding to msgid="5725254076556821247" --> + <string name="user_dict_settings_add_word_hint">Type a word</string> + <!-- User dictionary settings. Hint for the text field to type the optional shortcut to add to the user dictionary. [CHAR LIMIT=35] --> + <!-- This resource is corresponding to msgid="7333763456561873445" --> + <string name="user_dict_settings_add_shortcut_hint">Optional shortcut</string> + <!-- User dictionary settings. The title of the dialog to edit an existing word in the user dictionary. --> + <!-- This resource is corresponding to msgid="8967476444840548674" --> + <string name="user_dict_settings_edit_dialog_title">Edit word</string> + <!-- User dictionary settings. The title of the context menu item to edit the current word --> + <!-- This resource is corresponding to msgid="2210564879320004837" --> + <string name="user_dict_settings_context_menu_edit_title">Edit</string> + <!-- User dictionary settings. The title of the context menu item to delete the current word --> + <!-- This resource is corresponding to msgid="9140703913776549054" --> + <string name="user_dict_settings_context_menu_delete_title">Delete</string> + <!-- User dictionary settings. The text to show when there are no user-defined words in the dictionary [CHAR LIMIT=200] --> + <!-- This resource is corresponding to msgid="8165273379942105271" --> + <string name="user_dict_settings_empty_text">You don\'t have any words in the user dictionary. Add a word by touching the Add (+) button.</string> + <!-- User dictionary settings. The list item to choose to insert a word into the user dictionary for all languages --> + <!-- This resource is corresponding to msgid="6742000040975959247" --> + <string name="user_dict_settings_all_languages">For all languages</string> + <!-- User dictionary settings. The text to show for the option that shows the entire list of supported locales to choose one [CHAR LIMIT=30] --> + <!-- This resource is corresponding to msgid="7316375944684977910" --> + <string name="user_dict_settings_more_languages">More languages…</string> + <!-- User dictionary settings. Label to delete an entry in the user dictionary [CHAR LIMIT=30] + This resource is copied from packages/apps/Settings/res/values/strings.xml --> + <!-- This resource is corresponding to msgid="4219243412325163003" --> + <string name="user_dict_settings_delete">Delete</string> + <!-- User dictionary settings. Index of the user dictionary [CHAR LIMIT=30] + This resource is copied from packages/apps/Settings/res/values/strings.xml --> + <!-- This resource is corresponding to msgid="5433275485499039199" --> + <string name="user_dict_fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string> </resources> diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java index 88a27144c..c78064b23 100644 --- a/java/src/com/android/inputmethod/latin/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import android.app.Activity; import android.app.backup.BackupManager; import android.content.Context; import android.content.Intent; @@ -24,6 +25,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.media.AudioManager; +import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.ListPreference; @@ -31,17 +33,20 @@ import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; -import android.util.Log; import android.view.inputmethod.InputMethodSubtype; +import java.util.TreeSet; + import com.android.inputmethod.dictionarypack.DictionarySettingsActivity; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager; +import com.android.inputmethod.latin.userdictionary.UserDictionaryList; +import com.android.inputmethod.latin.userdictionary.UserDictionarySettings; import com.android.inputmethodcommon.InputMethodSettingsFragment; public final class SettingsFragment extends InputMethodSettingsFragment implements SharedPreferences.OnSharedPreferenceChangeListener { - private static final String TAG = SettingsFragment.class.getSimpleName(); + private static final boolean DBG_USE_INTERNAL_USER_SETTINGS = false; private ListPreference mVoicePreference; private ListPreference mShowCorrectionSuggestionsPreference; @@ -197,9 +202,13 @@ public final class SettingsFragment extends InputMethodSettingsFragment final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent(); final ResolveInfo ri = context.getPackageManager().resolveActivity( editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY); - if (ri == null) { - // TODO: Set a intent that invokes our own edit personal dictionary activity. - Log.w(TAG, "No activity that responds to " + editPersonalDictionaryIntent.getAction()); + if (DBG_USE_INTERNAL_USER_SETTINGS || ri == null) { + // TODO: Support ICS + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + updateUserDictionaryPreference(editPersonalDictionary); + } else { + removePreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY, getPreferenceScreen()); + } } if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) { @@ -408,4 +417,33 @@ public final class SettingsFragment extends InputMethodSettingsFragment } }); } + + private void updateUserDictionaryPreference(Preference userDictionaryPreference) { + final Activity activity = getActivity(); + final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity); + if (null == localeList) { + // The locale list is null if and only if the user dictionary service is + // not present or disabled. In this case we need to remove the preference. + getPreferenceScreen().removePreference(userDictionaryPreference); + } else if (localeList.size() <= 1) { + final Intent intent = + new Intent(UserDictionaryList.USER_DICTIONARY_SETTINGS_INTENT_ACTION); + userDictionaryPreference.setTitle(R.string.user_dict_single_settings_title); + userDictionaryPreference.setIntent(intent); + userDictionaryPreference.setFragment(UserDictionarySettings.class.getName()); + // If the size of localeList is 0, we don't set the locale parameter in the + // extras. This will be interpreted by the UserDictionarySettings class as + // meaning "the current locale". + // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesSet() + // the locale list always has at least one element, since it always includes the current + // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesSet(). + if (localeList.size() == 1) { + final String locale = (String)localeList.toArray()[0]; + userDictionaryPreference.getExtras().putString("locale", locale); + } + } else { + userDictionaryPreference.setTitle(R.string.user_dict_multiple_settings_title); + userDictionaryPreference.setFragment(UserDictionaryList.class.getName()); + } + } } diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java new file mode 100644 index 000000000..f0dc526e4 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.userdictionary; + +import com.android.inputmethod.latin.LocaleUtils; +import com.android.inputmethod.latin.R; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import android.provider.UserDictionary; +import android.text.TextUtils; +import android.view.View; +import android.widget.EditText; + +import java.util.ArrayList; +import java.util.Locale; +import java.util.TreeSet; + +// Caveat: This class is basically taken from +// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java +// in order to deal with some devices that have issues with the user dictionary handling + +/** + * A container class to factor common code to UserDictionaryAddWordFragment + * and UserDictionaryAddWordActivity. + */ +public class UserDictionaryAddWordContents { + public static final String EXTRA_MODE = "mode"; + public static final String EXTRA_WORD = "word"; + public static final String EXTRA_SHORTCUT = "shortcut"; + public static final String EXTRA_LOCALE = "locale"; + public static final String EXTRA_ORIGINAL_WORD = "originalWord"; + public static final String EXTRA_ORIGINAL_SHORTCUT = "originalShortcut"; + + public static final int MODE_EDIT = 0; + public static final int MODE_INSERT = 1; + + /* package */ static final int CODE_WORD_ADDED = 0; + /* package */ static final int CODE_CANCEL = 1; + /* package */ static final int CODE_ALREADY_PRESENT = 2; + + private static final int FREQUENCY_FOR_USER_DICTIONARY_ADDS = 250; + + private final int mMode; // Either MODE_EDIT or MODE_INSERT + private final EditText mWordEditText; + private final EditText mShortcutEditText; + private String mLocale; + private final String mOldWord; + private final String mOldShortcut; + + /* package */ UserDictionaryAddWordContents(final View view, final Bundle args) { + mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text); + mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut); + final String word = args.getString(EXTRA_WORD); + if (null != word) { + mWordEditText.setText(word); + mWordEditText.setSelection(word.length()); + } + final String shortcut = args.getString(EXTRA_SHORTCUT); + if (null != shortcut && null != mShortcutEditText) { + mShortcutEditText.setText(shortcut); + } + mMode = args.getInt(EXTRA_MODE); // default return value for #getInt() is 0 = MODE_EDIT + mOldWord = args.getString(EXTRA_WORD); + mOldShortcut = args.getString(EXTRA_SHORTCUT); + updateLocale(args.getString(EXTRA_LOCALE)); + } + + // locale may be null, this means default locale + // It may also be the empty string, which means "all locales" + /* package */ void updateLocale(final String locale) { + mLocale = null == locale ? Locale.getDefault().toString() : locale; + } + + /* package */ void saveStateIntoBundle(final Bundle outState) { + outState.putString(EXTRA_WORD, mWordEditText.getText().toString()); + outState.putString(EXTRA_ORIGINAL_WORD, mOldWord); + if (null != mShortcutEditText) { + outState.putString(EXTRA_SHORTCUT, mShortcutEditText.getText().toString()); + } + if (null != mOldShortcut) { + outState.putString(EXTRA_ORIGINAL_SHORTCUT, mOldShortcut); + } + outState.putString(EXTRA_LOCALE, mLocale); + } + + /* package */ void delete(final Context context) { + if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) { + // Mode edit: remove the old entry. + final ContentResolver resolver = context.getContentResolver(); + UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver); + } + // If we are in add mode, nothing was added, so we don't need to do anything. + } + + /* package */ int apply(final Context context, final Bundle outParameters) { + if (null != outParameters) saveStateIntoBundle(outParameters); + final ContentResolver resolver = context.getContentResolver(); + if (MODE_EDIT == mMode && !TextUtils.isEmpty(mOldWord)) { + // Mode edit: remove the old entry. + UserDictionarySettings.deleteWord(mOldWord, mOldShortcut, resolver); + } + final String newWord = mWordEditText.getText().toString(); + final String newShortcut; + if (null == mShortcutEditText) { + newShortcut = null; + } else { + final String tmpShortcut = mShortcutEditText.getText().toString(); + if (TextUtils.isEmpty(tmpShortcut)) { + newShortcut = null; + } else { + newShortcut = tmpShortcut; + } + } + if (TextUtils.isEmpty(newWord)) { + // If the word is somehow empty, don't insert it. + return CODE_CANCEL; + } + // If there is no shortcut, and the word already exists in the database, then we + // should not insert, because either A. the word exists with no shortcut, in which + // case the exact same thing we want to insert is already there, or B. the word + // exists with at least one shortcut, in which case it has priority on our word. + if (hasWord(newWord, context)) return CODE_ALREADY_PRESENT; + + // Disallow duplicates. If the same word with no shortcut is defined, remove it; if + // the same word with the same shortcut is defined, remove it; but we don't mind if + // there is the same word with a different, non-empty shortcut. + UserDictionarySettings.deleteWord(newWord, null, resolver); + if (!TextUtils.isEmpty(newShortcut)) { + // If newShortcut is empty we just deleted this, no need to do it again + UserDictionarySettings.deleteWord(newWord, newShortcut, resolver); + } + + // In this class we use the empty string to represent 'all locales' and mLocale cannot + // be null. However the addWord method takes null to mean 'all locales'. + UserDictionary.Words.addWord(context, newWord.toString(), + FREQUENCY_FOR_USER_DICTIONARY_ADDS, newShortcut, + TextUtils.isEmpty(mLocale) ? null : LocaleUtils.constructLocaleFromString(mLocale)); + + return CODE_WORD_ADDED; + } + + private static final String[] HAS_WORD_PROJECTION = { UserDictionary.Words.WORD }; + private static final String HAS_WORD_SELECTION_ONE_LOCALE = UserDictionary.Words.WORD + + "=? AND " + UserDictionary.Words.LOCALE + "=?"; + private static final String HAS_WORD_SELECTION_ALL_LOCALES = UserDictionary.Words.WORD + + "=? AND " + UserDictionary.Words.LOCALE + " is null"; + private boolean hasWord(final String word, final Context context) { + final Cursor cursor; + // mLocale == "" indicates this is an entry for all languages. Here, mLocale can't + // be null at all (it's ensured by the updateLocale method). + if ("".equals(mLocale)) { + cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, + HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ALL_LOCALES, + new String[] { word }, null /* sort order */); + } else { + cursor = context.getContentResolver().query(UserDictionary.Words.CONTENT_URI, + HAS_WORD_PROJECTION, HAS_WORD_SELECTION_ONE_LOCALE, + new String[] { word, mLocale }, null /* sort order */); + } + try { + if (null == cursor) return false; + return cursor.getCount() > 0; + } finally { + if (null != cursor) cursor.close(); + } + } + + public static class LocaleRenderer { + private final String mLocaleString; + private final String mDescription; + // LocaleString may NOT be null. + public LocaleRenderer(final Context context, final String localeString) { + mLocaleString = localeString; + if (null == localeString) { + mDescription = context.getString(R.string.user_dict_settings_more_languages); + } else if ("".equals(localeString)) { + mDescription = context.getString(R.string.user_dict_settings_all_languages); + } else { + mDescription = LocaleUtils.constructLocaleFromString(localeString).getDisplayName(); + } + } + @Override + public String toString() { + return mDescription; + } + public String getLocaleString() { + return mLocaleString; + } + // "More languages..." is null ; "All languages" is the empty string. + public boolean isMoreLanguages() { + return null == mLocaleString; + } + } + + private static void addLocaleDisplayNameToList(final Context context, + final ArrayList<LocaleRenderer> list, final String locale) { + if (null != locale) { + list.add(new LocaleRenderer(context, locale)); + } + } + + // Helper method to get the list of locales to display for this word + public ArrayList<LocaleRenderer> getLocalesList(final Activity activity) { + final TreeSet<String> locales = UserDictionaryList.getUserDictionaryLocalesSet(activity); + // Remove our locale if it's in, because we're always gonna put it at the top + locales.remove(mLocale); // mLocale may not be null + final String systemLocale = Locale.getDefault().toString(); + // The system locale should be inside. We want it at the 2nd spot. + locales.remove(systemLocale); // system locale may not be null + locales.remove(""); // Remove the empty string if it's there + final ArrayList<LocaleRenderer> localesList = new ArrayList<LocaleRenderer>(); + // Add the passed locale, then the system locale at the top of the list. Add an + // "all languages" entry at the bottom of the list. + addLocaleDisplayNameToList(activity, localesList, mLocale); + if (!systemLocale.equals(mLocale)) { + addLocaleDisplayNameToList(activity, localesList, systemLocale); + } + for (final String l : locales) { + // TODO: sort in unicode order + addLocaleDisplayNameToList(activity, localesList, l); + } + if (!"".equals(mLocale)) { + // If mLocale is "", then we already inserted the "all languages" item, so don't do it + addLocaleDisplayNameToList(activity, localesList, ""); // meaning: all languages + } + localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale + return localesList; + } +} diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java new file mode 100644 index 000000000..7970a365c --- /dev/null +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.userdictionary; + +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordContents.LocaleRenderer; +import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker.LocationChangedListener; + +import android.app.Fragment; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Spinner; + +import java.util.ArrayList; +import java.util.Locale; + +// Caveat: This class is basically taken from +// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordFragment.java +// in order to deal with some devices that have issues with the user dictionary handling + +/** + * Fragment to add a word/shortcut to the user dictionary. + * + * As opposed to the UserDictionaryActivity, this is only invoked within Settings + * from the UserDictionarySettings. + */ +public class UserDictionaryAddWordFragment extends Fragment + implements AdapterView.OnItemSelectedListener, LocationChangedListener { + + private static final int OPTIONS_MENU_DELETE = Menu.FIRST; + + private UserDictionaryAddWordContents mContents; + private View mRootView; + private boolean mIsDeleting = false; + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { + mRootView = inflater.inflate(R.layout.user_dictionary_add_word_fullscreen, null); + mIsDeleting = false; + if (null == mContents) { + mContents = new UserDictionaryAddWordContents(mRootView, getArguments()); + } + return mRootView; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + MenuItem actionItem = menu.add(0, OPTIONS_MENU_DELETE, 0, + R.string.user_dict_settings_delete).setIcon(android.R.drawable.ic_menu_delete); + actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + /** + * Callback for the framework when a menu option is pressed. + * + * This class only supports the delete menu item. + * @param MenuItem the item that was pressed + * @return false to allow normal menu processing to proceed, true to consume it here + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == OPTIONS_MENU_DELETE) { + mContents.delete(getActivity()); + mIsDeleting = true; + getActivity().onBackPressed(); + return true; + } + return false; + } + + @Override + public void onResume() { + super.onResume(); + // We are being shown: display the word + updateSpinner(); + } + + private void updateSpinner() { + final ArrayList<LocaleRenderer> localesList = mContents.getLocalesList(getActivity()); + + final Spinner localeSpinner = + (Spinner)mRootView.findViewById(R.id.user_dictionary_add_locale); + final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<LocaleRenderer>(getActivity(), + android.R.layout.simple_spinner_item, localesList); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + localeSpinner.setAdapter(adapter); + localeSpinner.setOnItemSelectedListener(this); + } + + @Override + public void onPause() { + super.onPause(); + // We are being hidden: commit changes to the user dictionary, unless we were deleting it + if (!mIsDeleting) { + mContents.apply(getActivity(), null); + } + } + + @Override + public void onItemSelected(final AdapterView<?> parent, final View view, final int pos, + final long id) { + final LocaleRenderer locale = (LocaleRenderer)parent.getItemAtPosition(pos); + if (locale.isMoreLanguages()) { + PreferenceActivity preferenceActivity = (PreferenceActivity)getActivity(); + preferenceActivity.startPreferenceFragment(new UserDictionaryLocalePicker(), true); + } else { + mContents.updateLocale(locale.getLocaleString()); + } + } + + @Override + public void onNothingSelected(final AdapterView<?> parent) { + // I'm not sure we can come here, but if we do, that's the right thing to do. + final Bundle args = getArguments(); + mContents.updateLocale(args.getString(UserDictionaryAddWordContents.EXTRA_LOCALE)); + } + + // Called by the locale picker + @Override + public void onLocaleSelected(final Locale locale) { + mContents.updateLocale(locale.toString()); + getActivity().onBackPressed(); + } +} diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java new file mode 100644 index 000000000..2d147aada --- /dev/null +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.userdictionary; + +import com.android.inputmethod.latin.LocaleUtils; +import com.android.inputmethod.latin.R; + +import android.app.Activity; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.PreferenceGroup; +import android.provider.UserDictionary; + +import java.util.Locale; +import java.util.TreeSet; + +// Caveat: This class is basically taken from +// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryList.java +// in order to deal with some devices that have issues with the user dictionary handling + +public class UserDictionaryList extends PreferenceFragment { + + public static final String USER_DICTIONARY_SETTINGS_INTENT_ACTION = + "android.settings.USER_DICTIONARY_SETTINGS"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity())); + } + + public static TreeSet<String> getUserDictionaryLocalesSet(Activity activity) { + @SuppressWarnings("deprecation") + final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI, + new String[] { UserDictionary.Words.LOCALE }, + null, null, null); + final TreeSet<String> localeList = new TreeSet<String>(); + if (null == cursor) { + // The user dictionary service is not present or disabled. Return null. + return null; + } else if (cursor.moveToFirst()) { + final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE); + do { + String locale = cursor.getString(columnIndex); + localeList.add(null != locale ? locale : ""); + } while (cursor.moveToNext()); + } + localeList.add(Locale.getDefault().toString()); + return localeList; + } + + /** + * Creates the entries that allow the user to go into the user dictionary for each locale. + * @param userDictGroup The group to put the settings in. + */ + protected void createUserDictSettings(PreferenceGroup userDictGroup) { + final Activity activity = getActivity(); + userDictGroup.removeAll(); + final TreeSet<String> localeList = + UserDictionaryList.getUserDictionaryLocalesSet(activity); + + if (localeList.isEmpty()) { + userDictGroup.addPreference(createUserDictionaryPreference(null, activity)); + } else { + for (String locale : localeList) { + userDictGroup.addPreference(createUserDictionaryPreference(locale, activity)); + } + } + } + + /** + * Create a single User Dictionary Preference object, with its parameters set. + * @param locale The locale for which this user dictionary is for. + * @return The corresponding preference. + */ + protected Preference createUserDictionaryPreference(String locale, Activity activity) { + final Preference newPref = new Preference(getActivity()); + final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION); + if (null == locale) { + newPref.setTitle(Locale.getDefault().getDisplayName()); + } else { + if ("".equals(locale)) + newPref.setTitle(getString(R.string.user_dict_settings_all_languages)); + else + newPref.setTitle(LocaleUtils.constructLocaleFromString(locale).getDisplayName()); + intent.putExtra("locale", locale); + newPref.getExtras().putString("locale", locale); + } + newPref.setIntent(intent); + newPref.setFragment(UserDictionarySettings.class.getName()); + return newPref; + } + + @Override + public void onResume() { + super.onResume(); + createUserDictSettings(getPreferenceScreen()); + } +} diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryLocalePicker.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryLocalePicker.java new file mode 100644 index 000000000..58d3fb91c --- /dev/null +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryLocalePicker.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.userdictionary; + +import android.app.Fragment; + +import java.util.Locale; + +// Caveat: This class is basically taken from +// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryLocalePicker.java +// in order to deal with some devices that have issues with the user dictionary handling + +public class UserDictionaryLocalePicker extends Fragment { + public UserDictionaryLocalePicker() { + super(); + // TODO: implement + } + + public interface LocationChangedListener { + public void onLocaleSelected(Locale locale); + } +} diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java new file mode 100644 index 000000000..a250c246e --- /dev/null +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java @@ -0,0 +1,286 @@ +/** + * Copyright (C) 2013 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. + */ + +package com.android.inputmethod.latin.userdictionary; + +import com.android.inputmethod.latin.R; + +import android.app.ListFragment; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.provider.UserDictionary; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AlphabetIndexer; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.SectionIndexer; +import android.widget.SimpleCursorAdapter; +import android.widget.TextView; + +import java.util.Locale; + +// Caveat: This class is basically taken from +// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionarySettings.java +// in order to deal with some devices that have issues with the user dictionary handling + +public class UserDictionarySettings extends ListFragment { + + private static final String[] QUERY_PROJECTION = { + UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT + }; + + // The index of the shortcut in the above array. + private static final int INDEX_SHORTCUT = 2; + + // Either the locale is empty (means the word is applicable to all locales) + // or the word equals our current locale + private static final String QUERY_SELECTION = + UserDictionary.Words.LOCALE + "=?"; + private static final String QUERY_SELECTION_ALL_LOCALES = + UserDictionary.Words.LOCALE + " is null"; + + private static final String DELETE_SELECTION_WITH_SHORTCUT = UserDictionary.Words.WORD + + "=? AND " + UserDictionary.Words.SHORTCUT + "=?"; + private static final String DELETE_SELECTION_WITHOUT_SHORTCUT = UserDictionary.Words.WORD + + "=? AND " + UserDictionary.Words.SHORTCUT + " is null OR " + + UserDictionary.Words.SHORTCUT + "=''"; + + private static final int OPTIONS_MENU_ADD = Menu.FIRST; + + private Cursor mCursor; + + protected String mLocale; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate( + R.layout.user_dictionary_preference_list_fragment, container, false); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Intent intent = getActivity().getIntent(); + final String localeFromIntent = + null == intent ? null : intent.getStringExtra("locale"); + + final Bundle arguments = getArguments(); + final String localeFromArguments = + null == arguments ? null : arguments.getString("locale"); + + final String locale; + if (null != localeFromArguments) { + locale = localeFromArguments; + } else if (null != localeFromIntent) { + locale = localeFromIntent; + } else { + locale = null; + } + + mLocale = locale; + mCursor = createCursor(locale); + TextView emptyView = (TextView) getView().findViewById(android.R.id.empty); + emptyView.setText(R.string.user_dict_settings_empty_text); + + final ListView listView = getListView(); + listView.setAdapter(createAdapter()); + listView.setFastScrollEnabled(true); + listView.setEmptyView(emptyView); + + setHasOptionsMenu(true); + + } + + @SuppressWarnings("deprecation") + private Cursor createCursor(final String locale) { + // Locale can be any of: + // - The string representation of a locale, as returned by Locale#toString() + // - The empty string. This means we want a cursor returning words valid for all locales. + // - null. This means we want a cursor for the current locale, whatever this is. + // Note that this contrasts with the data inside the database, where NULL means "all + // locales" and there should never be an empty string. The confusion is called by the + // historical use of null for "all locales". + // TODO: it should be easy to make this more readable by making the special values + // human-readable, like "all_locales" and "current_locales" strings, provided they + // can be guaranteed not to match locales that may exist. + if ("".equals(locale)) { + // Case-insensitive sort + return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, + QUERY_SELECTION_ALL_LOCALES, null, + "UPPER(" + UserDictionary.Words.WORD + ")"); + } else { + final String queryLocale = null != locale ? locale : Locale.getDefault().toString(); + return getActivity().managedQuery(UserDictionary.Words.CONTENT_URI, QUERY_PROJECTION, + QUERY_SELECTION, new String[] { queryLocale }, + "UPPER(" + UserDictionary.Words.WORD + ")"); + } + } + + private ListAdapter createAdapter() { + return new MyAdapter(getActivity(), + R.layout.user_dictionary_item, mCursor, + new String[] { UserDictionary.Words.WORD, UserDictionary.Words.SHORTCUT }, + new int[] { android.R.id.text1, android.R.id.text2 }, this); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + final String word = getWord(position); + final String shortcut = getShortcut(position); + if (word != null) { + showAddOrEditDialog(word, shortcut); + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + MenuItem actionItem = + menu.add(0, OPTIONS_MENU_ADD, 0, R.string.user_dict_settings_add_menu_title) + .setIcon(R.drawable.ic_menu_add); + actionItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | + MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == OPTIONS_MENU_ADD) { + showAddOrEditDialog(null, null); + return true; + } + return false; + } + + /** + * Add or edit a word. If editingWord is null, it's an add; otherwise, it's an edit. + * @param editingWord the word to edit, or null if it's an add. + * @param editingShortcut the shortcut for this entry, or null if none. + */ + private void showAddOrEditDialog(final String editingWord, final String editingShortcut) { + final Bundle args = new Bundle(); + args.putInt(UserDictionaryAddWordContents.EXTRA_MODE, null == editingWord + ? UserDictionaryAddWordContents.MODE_INSERT + : UserDictionaryAddWordContents.MODE_EDIT); + args.putString(UserDictionaryAddWordContents.EXTRA_WORD, editingWord); + args.putString(UserDictionaryAddWordContents.EXTRA_SHORTCUT, editingShortcut); + args.putString(UserDictionaryAddWordContents.EXTRA_LOCALE, mLocale); + android.preference.PreferenceActivity pa = + (android.preference.PreferenceActivity)getActivity(); + pa.startPreferencePanel(UserDictionaryAddWordFragment.class.getName(), + args, R.string.user_dict_settings_add_dialog_title, null, null, 0); + } + + private String getWord(final int position) { + if (null == mCursor) return null; + mCursor.moveToPosition(position); + // Handle a possible race-condition + if (mCursor.isAfterLast()) return null; + + return mCursor.getString( + mCursor.getColumnIndexOrThrow(UserDictionary.Words.WORD)); + } + + private String getShortcut(final int position) { + if (null == mCursor) return null; + mCursor.moveToPosition(position); + // Handle a possible race-condition + if (mCursor.isAfterLast()) return null; + + return mCursor.getString( + mCursor.getColumnIndexOrThrow(UserDictionary.Words.SHORTCUT)); + } + + public static void deleteWord(final String word, final String shortcut, + final ContentResolver resolver) { + if (TextUtils.isEmpty(shortcut)) { + resolver.delete( + UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITHOUT_SHORTCUT, + new String[] { word }); + } else { + resolver.delete( + UserDictionary.Words.CONTENT_URI, DELETE_SELECTION_WITH_SHORTCUT, + new String[] { word, shortcut }); + } + } + + private static class MyAdapter extends SimpleCursorAdapter implements SectionIndexer { + + private AlphabetIndexer mIndexer; + + private ViewBinder mViewBinder = new ViewBinder() { + + @Override + public boolean setViewValue(View v, Cursor c, int columnIndex) { + if (columnIndex == INDEX_SHORTCUT) { + final String shortcut = c.getString(INDEX_SHORTCUT); + if (TextUtils.isEmpty(shortcut)) { + v.setVisibility(View.GONE); + } else { + ((TextView)v).setText(shortcut); + v.setVisibility(View.VISIBLE); + } + v.invalidate(); + return true; + } + + return false; + } + }; + + @SuppressWarnings("deprecation") + public MyAdapter(Context context, int layout, Cursor c, String[] from, int[] to, + UserDictionarySettings settings) { + super(context, layout, c, from, to); + + if (null != c) { + final String alphabet = context.getString(R.string.user_dict_fast_scroll_alphabet); + final int wordColIndex = c.getColumnIndexOrThrow(UserDictionary.Words.WORD); + mIndexer = new AlphabetIndexer(c, wordColIndex, alphabet); + } + setViewBinder(mViewBinder); + } + + @Override + public int getPositionForSection(int section) { + return null == mIndexer ? 0 : mIndexer.getPositionForSection(section); + } + + @Override + public int getSectionForPosition(int position) { + return null == mIndexer ? 0 : mIndexer.getSectionForPosition(position); + } + + @Override + public Object[] getSections() { + return null == mIndexer ? null : mIndexer.getSections(); + } + } +} |