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