diff --git a/android/res/layout/voice_variant_preference.xml b/android/res/layout/voice_variant_preference.xml new file mode 100644 index 00000000..f593dc89 --- /dev/null +++ b/android/res/layout/voice_variant_preference.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/res/values/donottranslate.xml b/android/res/values/donottranslate.xml index 86437a09..75d1f9fc 100644 --- a/android/res/values/donottranslate.xml +++ b/android/res/values/donottranslate.xml @@ -6,32 +6,7 @@ --> eSpeak - - male - m1 - m2 - m3 - m4 - m5 - m6 - m7 - female - f1 - f2 - f3 - f4 - f5 - klatt - klatt2 - klatt3 - klatt4 - croak - whisper - whisperf - male-young - female-young - male-old - female-old - + + Klatt \ No newline at end of file diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml index 7a2f5909..f84d1fca 100644 --- a/android/res/values/strings.xml +++ b/android/res/values/strings.xml @@ -65,41 +65,6 @@ Description: Sample text spoken aloud when the user is trying out a language. --> This is a sample of text spoken in %s - - - Male - Male (Variant 1) - Male (Variant 2) - Male (Variant 3) - Male (Variant 4) - Male (Variant 5) - Male (Variant 6) - Male (Variant 7) - Female - Female (Variant 1) - Female (Variant 2) - Female (Variant 3) - Female (Variant 4) - Female (Variant 5) - Klatt (Variant 1) - Klatt (Variant 2) - Klatt (Variant 3) - Klatt (Variant 4) - Croak (Male) - Whisper (Male) - Whisper (Female) - Young (Male) - Young (Female) - Old (Male) - Old (Female) - - Status eSpeak version Speak @@ -117,4 +82,14 @@ Custom: %s None Punctuation characters + Category + Variant + Male + Female + Default + Variant %d + Young + Old + Croak + Whisper diff --git a/android/res/xml/preferences.xml b/android/res/xml/preferences.xml index fe20d547..5c099e78 100644 --- a/android/res/xml/preferences.xml +++ b/android/res/xml/preferences.xml @@ -1,12 +1,3 @@ - - - \ No newline at end of file diff --git a/android/src/com/reecedunn/espeak/ResourceIdListAdapter.java b/android/src/com/reecedunn/espeak/ResourceIdListAdapter.java new file mode 100644 index 00000000..844422e5 --- /dev/null +++ b/android/src/com/reecedunn/espeak/ResourceIdListAdapter.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 Reece H. Dunn + * + * 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.reecedunn.espeak; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +public class ResourceIdListAdapter extends ArrayAdapter +{ + private final LayoutInflater mInflater; + + static class ViewHolder + { + public TextView text; + } + + public ResourceIdListAdapter(Activity context, Integer[] resources) + { + super(context, android.R.layout.simple_list_item_1, resources); + mInflater = context.getLayoutInflater(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + ViewHolder holder; + if (convertView == null) + { + convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false); + holder = new ViewHolder(); + holder.text = (TextView)convertView.findViewById(android.R.id.text1); + convertView.setTag(holder); + } + else + { + holder = (ViewHolder)convertView.getTag(); + } + + holder.text.setText(getItem(position)); + return convertView; + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) + { + return getView(position, convertView, parent); + } +} diff --git a/android/src/com/reecedunn/espeak/TtsSettingsActivity.java b/android/src/com/reecedunn/espeak/TtsSettingsActivity.java index fc090772..343b63b0 100644 --- a/android/src/com/reecedunn/espeak/TtsSettingsActivity.java +++ b/android/src/com/reecedunn/espeak/TtsSettingsActivity.java @@ -97,7 +97,19 @@ public class TtsSettingsActivity extends PreferenceActivity { } } - private static Preference createSpeakPunctuationPreference(Context context, SpeechSynthesis engine, int titleRes) { + private static Preference createVoiceVariantPreference(Context context, VoiceSettings settings, int titleRes) { + final String title = context.getString(titleRes); + + final VoiceVariantPreference pref = new VoiceVariantPreference(context); + pref.setTitle(title); + pref.setDialogTitle(title); + pref.setOnPreferenceChangeListener(mOnPreferenceChanged); + pref.setPersistent(true); + pref.setVoiceVariant(settings.getVoiceVariant()); + return pref; + } + + private static Preference createSpeakPunctuationPreference(Context context, VoiceSettings settings, int titleRes) { final String title = context.getString(titleRes); final SpeakPunctuationPreference pref = new SpeakPunctuationPreference(context); @@ -105,7 +117,7 @@ public class TtsSettingsActivity extends PreferenceActivity { pref.setDialogTitle(title); pref.setOnPreferenceChangeListener(mOnPreferenceChanged); pref.setPersistent(true); - pref.setVoiceSettings(new VoiceSettings(PreferenceManager.getDefaultSharedPreferences(context), engine)); + pref.setVoiceSettings(settings); return pref; } @@ -153,31 +165,11 @@ public class TtsSettingsActivity extends PreferenceActivity { * summary with the current entry value. */ private static void createPreferences(Context context, PreferenceGroup group) { - if (group == null) { - return; - } - - final int count = group.getPreferenceCount(); - - for (int i = 0; i < count; i++) { - final Preference preference = group.getPreference(i); - - if (preference instanceof PreferenceGroup) { - createPreferences(null, (PreferenceGroup) preference); - } else if (preference instanceof ListPreference) { - preference.setOnPreferenceChangeListener(mOnPreferenceChanged); - } - } - - if (context == null) { - return; - } - - // Bind eSpeak parameters to preference settings: - SpeechSynthesis engine = new SpeechSynthesis(context, null); + VoiceSettings settings = new VoiceSettings(PreferenceManager.getDefaultSharedPreferences(context), engine); - group.addPreference(createSpeakPunctuationPreference(context, engine, R.string.espeak_speak_punctuation)); + group.addPreference(createVoiceVariantPreference(context, settings, R.string.espeak_variant)); + group.addPreference(createSpeakPunctuationPreference(context, settings, R.string.espeak_speak_punctuation)); group.addPreference(createSeekBarPreference(context, engine.Rate, "espeak_rate", R.string.setting_default_rate)); group.addPreference(createSeekBarPreference(context, engine.Pitch, "espeak_pitch", R.string.setting_default_pitch)); group.addPreference(createSeekBarPreference(context, engine.PitchRange, "espeak_pitch_range", R.string.espeak_pitch_range)); diff --git a/android/src/com/reecedunn/espeak/VoiceVariant.java b/android/src/com/reecedunn/espeak/VoiceVariant.java index 006b6acf..e2a92f76 100644 --- a/android/src/com/reecedunn/espeak/VoiceVariant.java +++ b/android/src/com/reecedunn/espeak/VoiceVariant.java @@ -60,6 +60,17 @@ public class VoiceVariant { return ret; } + public boolean equals(Object o) { + if (o instanceof VoiceVariant) { + VoiceVariant other = (VoiceVariant)o; + if (variant == null || other.variant == null) { + return other.variant == null && variant == null && other.gender == gender && other.age == age; + } + return other.variant.equals(variant) && other.gender == gender && other.age == age; + } + return false; + } + public static VoiceVariant parseVoiceVariant(String value) { String[] parts = mVariantPattern.split(value); int age = SpeechSynthesis.AGE_ANY; diff --git a/android/src/com/reecedunn/espeak/VoiceVariantPreference.java b/android/src/com/reecedunn/espeak/VoiceVariantPreference.java new file mode 100644 index 00000000..93f5d45e --- /dev/null +++ b/android/src/com/reecedunn/espeak/VoiceVariantPreference.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2013 Reece H. Dunn + * + * 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.reecedunn.espeak; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.preference.DialogPreference; +import android.text.Editable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.RadioButton; +import android.widget.Spinner; +import android.widget.TextView; + +import java.util.List; + +public class VoiceVariantPreference extends DialogPreference { + private Spinner mCategory; + private Spinner mVariant; + + private int mCategoryIndex = 0; + private int mVariantIndex = 0; + + static class ViewHolder + { + public TextView text; + } + + private class VariantData { + private final int name; + private final Object arg; + private final VoiceVariant variant; + + protected VariantData(int name, String variant) { + this(name, null, variant); + } + + protected VariantData(int name, Object arg, String variant) { + this.name = name; + this.arg = arg; + this.variant = VoiceVariant.parseVoiceVariant(variant); + } + + public String getDisplayName(Context context) { + String text = context.getText(name).toString(); + if (arg == null) { + return text; + } + return String.format(text, arg); + } + + public VoiceVariant getVariant() { + return variant; + } + } + + public class VariantDataListAdapter extends ArrayAdapter + { + private final LayoutInflater mInflater; + + public VariantDataListAdapter(Activity context, VariantData[] resources) + { + super(context, android.R.layout.simple_list_item_1, resources); + mInflater = context.getLayoutInflater(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + ViewHolder holder; + if (convertView == null) + { + convertView = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false); + holder = new ViewHolder(); + holder.text = (TextView)convertView.findViewById(android.R.id.text1); + convertView.setTag(holder); + } + else + { + holder = (ViewHolder)convertView.getTag(); + } + + holder.text.setText(getItem(position).getDisplayName(getContext())); + return convertView; + } + + @Override + public View getDropDownView(int position, View convertView, ViewGroup parent) + { + return getView(position, convertView, parent); + } + } + + private Integer[] categories = { + R.string.variant_male, + R.string.variant_female, + R.string.variant_klatt, + R.string.variant_young, + R.string.variant_old, + R.string.variant_croak, + R.string.variant_whisper, + }; + + private VariantData[][] variants = { + { // Male + new VariantData(R.string.variant_default, "male"), + new VariantData(R.string.variant_n, 1, "m1"), + new VariantData(R.string.variant_n, 2, "m2"), + new VariantData(R.string.variant_n, 3, "m3"), + new VariantData(R.string.variant_n, 4, "m4"), + new VariantData(R.string.variant_n, 5, "m5"), + new VariantData(R.string.variant_n, 6, "m6"), + new VariantData(R.string.variant_n, 7, "m7"), + },{ // Female + new VariantData(R.string.variant_default, "female"), + new VariantData(R.string.variant_n, 1, "f1"), + new VariantData(R.string.variant_n, 2, "f2"), + new VariantData(R.string.variant_n, 3, "f3"), + new VariantData(R.string.variant_n, 4, "f4"), + new VariantData(R.string.variant_n, 5, "f5"), + },{ // Klatt + new VariantData(R.string.variant_n, 1, "klatt"), + new VariantData(R.string.variant_n, 2, "klatt2"), + new VariantData(R.string.variant_n, 3, "klatt3"), + new VariantData(R.string.variant_n, 4, "klatt4"), + },{ // Young + new VariantData(R.string.variant_male, "male-young"), + new VariantData(R.string.variant_female, "female-young"), + },{ // Old + new VariantData(R.string.variant_male, "male-old"), + new VariantData(R.string.variant_female, "female-old"), + },{ // Croak + new VariantData(R.string.variant_male, "croak"), + },{ // Whisper + new VariantData(R.string.variant_male, "whisper"), + new VariantData(R.string.variant_female, "whisperf"), + }, + }; + + public VoiceVariantPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setDialogLayoutResource(R.layout.voice_variant_preference); + setLayoutResource(R.layout.information_view); + setPositiveButtonText(android.R.string.ok); + setNegativeButtonText(android.R.string.cancel); + } + + public VoiceVariantPreference(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public VoiceVariantPreference(Context context) { + this(context, null); + } + + public void setVoiceVariant(VoiceVariant variant) { + for (int i = 0; i < variants.length; ++i) { + VariantData[] items = variants[i]; + for (int j = 0; j < items.length; ++j) { + if (items[j].getVariant().equals(variant)) { + mCategoryIndex = i; + mVariantIndex = j; + onDataChanged(); + return; + } + } + } + onDataChanged(); + } + + @Override + protected View onCreateDialogView() { + View root = super.onCreateDialogView(); + mCategory = (Spinner)root.findViewById(R.id.category); + mVariant = (Spinner)root.findViewById(R.id.variant); + return root; + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + // Cache the indices so they don't get overwritten by the OnItemSelectedListener handlers. + final int category = mCategoryIndex; + final int variant = mVariantIndex; + + mCategory.setAdapter(new ResourceIdListAdapter((Activity)getContext(), categories)); + mCategory.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + private boolean mInitializing = true; + + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long id) { + mVariant.setAdapter(new VariantDataListAdapter((Activity) getContext(), variants[position])); + if (mInitializing) { + mVariant.setSelection(variant); + mInitializing = false; + } + mCategoryIndex = position; + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + } + }); + mVariant.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long id) { + mVariantIndex = position; + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + } + }); + + mCategory.setSelection(category); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + onDataChanged(); + if (shouldCommit()) { + SharedPreferences.Editor editor = getEditor(); + if (editor != null) { + VoiceVariant variant = variants[mCategoryIndex][mVariantIndex].getVariant(); + editor.putString(VoiceSettings.PREF_VARIANT, variant.toString()); + editor.commit(); + } + } + break; + } + super.onClick(dialog, which); + } + + private void onDataChanged() { + Context context = getContext(); + CharSequence category = context.getText(categories[mCategoryIndex]); + CharSequence variant = variants[mCategoryIndex][mVariantIndex].getDisplayName(context); + callChangeListener(String.format("%s (%s)", category, variant)); + } +}