1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.chrome.browser.infobar; 6 7import android.content.Context; 8import android.graphics.Color; 9import android.text.SpannableString; 10import android.text.TextUtils; 11import android.text.style.ForegroundColorSpan; 12import android.view.LayoutInflater; 13import android.view.View; 14import android.view.ViewGroup; 15import android.widget.AdapterView; 16import android.widget.ArrayAdapter; 17import android.widget.FrameLayout; 18import android.widget.Spinner; 19import android.widget.TextView; 20 21import org.chromium.chrome.R; 22 23import java.util.ArrayList; 24import java.util.List; 25 26/** 27 * Language panel shown in the translate infobar. 28 */ 29public class TranslateLanguagePanel 30 implements TranslateSubPanel, AdapterView.OnItemSelectedListener { 31 32 private static final int LANGUAGE_TYPE_SOURCE = 0; 33 private static final int LANGUAGE_TYPE_TARGET = 1; 34 35 // UI elements. 36 private Spinner mSourceSpinner; 37 private Spinner mTargetSpinner; 38 39 // Items that are not interacted with. 40 // Provided by the caller, the new languages will be set here if the user 41 // clicks "done". 42 private final TranslateOptions mOptions; 43 44 // This object will be used to keep the state for the time the 45 // panel is opened it can be totally discarded in the end if the user 46 // clicks "cancel". 47 private final TranslateOptions mSessionOptions; 48 49 private LanguageArrayAdapter mSourceAdapter; 50 private LanguageArrayAdapter mTargetAdapter; 51 52 private final SubPanelListener mListener; 53 54 /** 55 * Display language drop downs so they can be picked as source or 56 * target for a translation. 57 * 58 * @param listener triggered when the panel is closed 59 * @param options will be modified with the new languages selected. 60 */ 61 public TranslateLanguagePanel(SubPanelListener listener, TranslateOptions options) { 62 mListener = listener; 63 mOptions = options; 64 mSessionOptions = new TranslateOptions(mOptions); 65 } 66 67 @Override 68 public void createContent(Context context, InfoBarLayout layout) { 69 mSourceSpinner = null; 70 mTargetSpinner = null; 71 72 String changeLanguage = context.getString(R.string.translate_infobar_change_languages); 73 layout.setMessage(changeLanguage); 74 75 // Set up the spinners. 76 createSpinners(context); 77 layout.setCustomContent(mSourceSpinner, mTargetSpinner); 78 79 // Set up the buttons. 80 layout.setButtons(context.getString(R.string.translate_button_done), 81 context.getString(R.string.cancel)); 82 } 83 84 @Override 85 public void onButtonClicked(boolean primary) { 86 if (primary) { 87 mOptions.setSourceLanguage(mSessionOptions.sourceLanguageIndex()); 88 mOptions.setTargetLanguage(mSessionOptions.targetLanguageIndex()); 89 } 90 mListener.onPanelClosed(InfoBar.ACTION_TYPE_NONE); 91 } 92 93 private void createSpinners(Context context) { 94 mSourceAdapter = new LanguageArrayAdapter(context, R.layout.translate_spinner, 95 LANGUAGE_TYPE_SOURCE); 96 mTargetAdapter = new LanguageArrayAdapter(context, R.layout.translate_spinner, 97 LANGUAGE_TYPE_TARGET); 98 99 // Determine how wide each spinner needs to be to avoid truncating its children. 100 mSourceAdapter.addAll(createSpinnerLanguages(-1)); 101 mTargetAdapter.addAll(createSpinnerLanguages(-1)); 102 mSourceAdapter.measureWidthRequiredForView(); 103 mTargetAdapter.measureWidthRequiredForView(); 104 105 // Create the spinners. 106 mSourceSpinner = new Spinner(context); 107 mTargetSpinner = new Spinner(context); 108 mSourceSpinner.setOnItemSelectedListener(this); 109 mTargetSpinner.setOnItemSelectedListener(this); 110 mSourceSpinner.setAdapter(mSourceAdapter); 111 mTargetSpinner.setAdapter(mTargetAdapter); 112 reloadSpinners(); 113 } 114 115 private void reloadSpinners() { 116 mSourceAdapter.clear(); 117 mTargetAdapter.clear(); 118 119 int sourceAvoidLanguage = mSessionOptions.targetLanguageIndex(); 120 int targetAvoidLanguage = mSessionOptions.sourceLanguageIndex(); 121 mSourceAdapter.addAll(createSpinnerLanguages(sourceAvoidLanguage)); 122 mTargetAdapter.addAll(createSpinnerLanguages(targetAvoidLanguage)); 123 124 int originalSourceSelection = mSourceSpinner.getSelectedItemPosition(); 125 int newSourceSelection = getSelectionPosition(LANGUAGE_TYPE_SOURCE); 126 if (originalSourceSelection != newSourceSelection) 127 mSourceSpinner.setSelection(newSourceSelection); 128 129 int originalTargetSelection = mTargetSpinner.getSelectedItemPosition(); 130 int newTargetSelection = getSelectionPosition(LANGUAGE_TYPE_TARGET); 131 if (originalTargetSelection != newTargetSelection) 132 mTargetSpinner.setSelection(newTargetSelection); 133 } 134 135 private int getSelectionPosition(int languageType) { 136 int position = languageType == LANGUAGE_TYPE_SOURCE ? mSessionOptions.sourceLanguageIndex() 137 : mSessionOptions.targetLanguageIndex(); 138 139 // Since the source and target languages cannot appear in both spinners, the index for the 140 // source language can be off by one if comes after the target language alphabetically (and 141 // vice versa). 142 int opposite = languageType == LANGUAGE_TYPE_SOURCE ? mSessionOptions.targetLanguageIndex() 143 : mSessionOptions.sourceLanguageIndex(); 144 if (opposite < position) position -= 1; 145 146 return position; 147 } 148 149 @Override 150 public void onItemSelected(AdapterView<?> adapter, View view, int position, long id) { 151 Spinner spinner = (Spinner) adapter; 152 int newId = ((SpinnerLanguageElement) spinner.getSelectedItem()).getLanguageId(); 153 if (spinner == mSourceSpinner) { 154 mSessionOptions.setSourceLanguage(newId); 155 } else { 156 mSessionOptions.setTargetLanguage(newId); 157 } 158 reloadSpinners(); 159 } 160 161 @Override 162 public void onNothingSelected(AdapterView<?> adapter) { 163 } 164 165 /** 166 * Determines what languages will be shown in the Spinner. 167 * @param avoidLanguage Index of the language to avoid. Use -1 to display all languages. 168 */ 169 private ArrayList<SpinnerLanguageElement> createSpinnerLanguages(int avoidLanguage) { 170 ArrayList<SpinnerLanguageElement> result = new ArrayList<SpinnerLanguageElement>(); 171 List<String> languages = mSessionOptions.allLanguages(); 172 for (int i = 0; i < languages.size(); ++i) { 173 if (i != avoidLanguage) { 174 result.add(new SpinnerLanguageElement(languages.get(i), i)); 175 } 176 } 177 return result; 178 } 179 180 /** 181 * The drop down view displayed to show the currently selected value. 182 */ 183 private static class LanguageArrayAdapter extends ArrayAdapter<SpinnerLanguageElement> { 184 private final SpannableString mTextTemplate; 185 private int mMinimumWidth; 186 187 public LanguageArrayAdapter(Context context, int textViewResourceId, 188 int languageType) { 189 super(context, textViewResourceId); 190 191 // Get the string that we will display inside the Spinner, indicating whether the 192 // spinner is used for the source or target language. 193 String textTemplate = languageType == LANGUAGE_TYPE_SOURCE 194 ? context.getString(R.string.translate_options_source_hint) 195 : context.getString(R.string.translate_options_target_hint); 196 mTextTemplate = new SpannableString(textTemplate); 197 mTextTemplate.setSpan( 198 new ForegroundColorSpan(Color.GRAY), 0, textTemplate.length(), 0); 199 } 200 201 /** Measures how large the view needs to be to avoid truncating its children. */ 202 public void measureWidthRequiredForView() { 203 mMinimumWidth = 0; 204 205 final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 206 207 FrameLayout layout = new FrameLayout(getContext()); 208 TextView estimator = (TextView) LayoutInflater.from(getContext()).inflate( 209 R.layout.infobar_text, null); 210 layout.addView(estimator); 211 for (int i = 0; i < getCount(); ++i) { 212 estimator.setText(getStringForLanguage(i)); 213 estimator.measure(spec, spec); 214 mMinimumWidth = Math.max(mMinimumWidth, estimator.getMeasuredWidth()); 215 } 216 } 217 218 @Override 219 public View getDropDownView(int position, View convertView, ViewGroup parent) { 220 TextView result; 221 if (!(convertView instanceof TextView)) { 222 result = (TextView) LayoutInflater.from(getContext()).inflate( 223 R.layout.infobar_spinner_item, null); 224 } else { 225 result = (TextView) convertView; 226 } 227 228 String language = getItem(position).toString(); 229 result.setText(language); 230 return result; 231 } 232 233 @Override 234 public View getView(int position, View convertView, ViewGroup parent) { 235 TextView result; 236 if (!(convertView instanceof TextView)) { 237 result = (TextView) LayoutInflater.from(getContext()).inflate( 238 R.layout.infobar_text, null); 239 } else { 240 result = (TextView) convertView; 241 } 242 result.setEllipsize(TextUtils.TruncateAt.END); 243 result.setMaxLines(1); 244 result.setText(getStringForLanguage(position)); 245 result.setMinWidth(mMinimumWidth); 246 return result; 247 } 248 249 private CharSequence getStringForLanguage(int position) { 250 // The spinners prepend a string to show if they're for the source or target language. 251 String language = getItem(position).toString(); 252 SpannableString lang = new SpannableString(language); 253 lang.setSpan(new ForegroundColorSpan(Color.BLACK), 0, lang.length(), 0); 254 return TextUtils.expandTemplate(mTextTemplate, lang); 255 } 256 } 257 258 /** 259 * The element that goes inside the spinner. 260 */ 261 private static class SpinnerLanguageElement { 262 private final String mLanguageName; 263 private final int mLanguageId; 264 265 public SpinnerLanguageElement(String languageName, int languageId) { 266 mLanguageName = languageName; 267 mLanguageId = languageId; 268 } 269 270 public int getLanguageId() { 271 return mLanguageId; 272 } 273 274 /** 275 * This is the text displayed in the spinner element so make sure no debug information 276 * is added. 277 */ 278 @Override 279 public String toString() { 280 return mLanguageName; 281 } 282 } 283} 284