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 5 package org.chromium.chrome.browser.infobar; 6 7 import android.content.Context; 8 import android.text.Spannable; 9 import android.text.SpannableString; 10 import android.text.SpannableStringBuilder; 11 import android.text.TextUtils; 12 import android.text.style.ClickableSpan; 13 import android.view.View; 14 import android.widget.CheckBox; 15 16 import org.chromium.chrome.R; 17 import org.chromium.ui.base.DeviceFormFactor; 18 19 /** 20 * Java version of the translate infobar 21 */ 22 public class TranslateInfoBar extends InfoBar implements SubPanelListener { 23 // Needs to be kept in sync with the Type enum in translate_infobar_delegate.h. 24 public static final int BEFORE_TRANSLATE_INFOBAR = 0; 25 public static final int TRANSLATING_INFOBAR = 1; 26 public static final int AFTER_TRANSLATE_INFOBAR = 2; 27 public static final int TRANSLATE_ERROR_INFOBAR = 3; 28 public static final int MAX_INFOBAR_INDEX = 4; 29 30 // Defines what subpanel needs to be shown, if any 31 public static final int NO_PANEL = 0; 32 public static final int LANGUAGE_PANEL = 1; 33 public static final int NEVER_PANEL = 2; 34 public static final int ALWAYS_PANEL = 3; 35 public static final int MAX_PANEL_INDEX = 4; 36 37 private int mInfoBarType; 38 private final TranslateOptions mOptions; 39 private int mOptionsPanelViewType; 40 private TranslateSubPanel mSubPanel; 41 private final boolean mShouldShowNeverBar; 42 private final TranslateInfoBarDelegate mTranslateDelegate; 43 44 public TranslateInfoBar(long nativeInfoBarPtr, TranslateInfoBarDelegate delegate, 45 int infoBarType, int sourceLanguageIndex, int targetLanguageIndex, 46 boolean autoTranslatePair, boolean shouldShowNeverBar, 47 boolean triggeredFromMenu, String[] languages) { 48 super(null, R.drawable.infobar_translate, null); 49 mTranslateDelegate = delegate; 50 mOptions = new TranslateOptions(sourceLanguageIndex, targetLanguageIndex, languages, 51 autoTranslatePair, triggeredFromMenu); 52 mInfoBarType = infoBarType; 53 mShouldShowNeverBar = shouldShowNeverBar; 54 mOptionsPanelViewType = NO_PANEL; 55 setNativeInfoBar(nativeInfoBarPtr); 56 } 57 58 @Override 59 public void onCloseButtonClicked() { 60 if (getInfoBarType() == BEFORE_TRANSLATE_INFOBAR && mOptionsPanelViewType == NO_PANEL) { 61 // Make it behave exactly as the Nope Button. 62 onButtonClicked(false); 63 } else { 64 nativeOnCloseButtonClicked(mNativeInfoBarPtr); 65 } 66 } 67 68 @Override 69 public void onButtonClicked(boolean isPrimaryButton) { 70 if (mSubPanel != null) { 71 mSubPanel.onButtonClicked(isPrimaryButton); 72 return; 73 } 74 75 int action = actionFor(isPrimaryButton); 76 77 if (getInfoBarType() == BEFORE_TRANSLATE_INFOBAR && mOptionsPanelViewType == NO_PANEL 78 && action == ACTION_TYPE_CANCEL && needsNeverPanel()) { 79 // "Nope" was clicked and instead of dismissing we need to show 80 // the extra never panel. 81 swapPanel(NEVER_PANEL); 82 } else { 83 onTranslateInfoBarButtonClicked(action); 84 } 85 } 86 87 /** 88 * Based on the infobar and the button pressed figure out what action needs to happen. 89 */ 90 private int actionFor(boolean isPrimaryButton) { 91 int action = InfoBar.ACTION_TYPE_NONE; 92 int infobarType = getInfoBarType(); 93 switch (infobarType) { 94 case TranslateInfoBar.BEFORE_TRANSLATE_INFOBAR: 95 action = isPrimaryButton 96 ? InfoBar.ACTION_TYPE_TRANSLATE : InfoBar.ACTION_TYPE_CANCEL; 97 break; 98 case TranslateInfoBar.AFTER_TRANSLATE_INFOBAR: 99 if (!isPrimaryButton) { 100 action = InfoBar.ACTION_TYPE_TRANSLATE_SHOW_ORIGINAL; 101 } 102 break; 103 case TranslateInfoBar.TRANSLATE_ERROR_INFOBAR: 104 // retry 105 action = InfoBar.ACTION_TYPE_TRANSLATE; 106 break; 107 default: 108 break; 109 } 110 return action; 111 } 112 113 private CharSequence getMessageText(Context context) { 114 switch (getInfoBarType()) { 115 case BEFORE_TRANSLATE_INFOBAR: 116 String template = context.getString(R.string.translate_infobar_text); 117 return formatBeforeInfoBarMessage(template, mOptions.sourceLanguage(), 118 mOptions.targetLanguage(), LANGUAGE_PANEL); 119 case AFTER_TRANSLATE_INFOBAR: 120 String translated = context.getString( 121 R.string.translate_infobar_translation_done, mOptions.targetLanguage()); 122 if (needsAlwaysPanel()) { 123 String moreOptions = context.getString( 124 R.string.translate_infobar_translation_more_options); 125 return formatAfterTranslateInfoBarMessage(translated, moreOptions, 126 ALWAYS_PANEL); 127 } else { 128 return translated; 129 } 130 case TRANSLATING_INFOBAR: 131 return context.getString(R.string.translate_infobar_translating, 132 mOptions.targetLanguage()); 133 default: 134 return context.getString(R.string.translate_infobar_error); 135 } 136 } 137 138 private String getPrimaryButtonText(Context context) { 139 switch (getInfoBarType()) { 140 case BEFORE_TRANSLATE_INFOBAR: 141 return context.getString(R.string.translate_button); 142 case AFTER_TRANSLATE_INFOBAR: 143 if (!needsAlwaysPanel()) { 144 return context.getString(R.string.translate_button_done); 145 } 146 return null; 147 case TRANSLATE_ERROR_INFOBAR: 148 return context.getString(R.string.translate_retry); 149 default: 150 return null; // no inner buttons on the remaining infobars 151 } 152 } 153 154 private String getSecondaryButtonText(Context context) { 155 switch (getInfoBarType()) { 156 case BEFORE_TRANSLATE_INFOBAR: 157 return context.getString(R.string.translate_nope); 158 case AFTER_TRANSLATE_INFOBAR: 159 if (!needsAlwaysPanel()) { 160 return context.getString(R.string.translate_show_original); 161 } 162 return null; 163 default: 164 return null; 165 } 166 } 167 168 @Override 169 public void createContent(InfoBarLayout layout) { 170 if (mOptionsPanelViewType == NO_PANEL) { 171 mSubPanel = null; 172 } else { 173 mSubPanel = panelFor(mOptionsPanelViewType); 174 if (mSubPanel != null) { 175 mSubPanel.createContent(getContext(), layout); 176 } 177 return; 178 } 179 180 Context context = layout.getContext(); 181 layout.setMessage(getMessageText(context)); 182 layout.setButtons(getPrimaryButtonText(context), getSecondaryButtonText(context)); 183 184 if (getInfoBarType() == AFTER_TRANSLATE_INFOBAR && 185 !needsAlwaysPanel() && 186 !mOptions.triggeredFromMenu()) { 187 // Long always translate version 188 TranslateCheckBox checkBox = new TranslateCheckBox(context, mOptions, this); 189 layout.setCustomContent(checkBox); 190 } 191 } 192 193 // SubPanelListener implementation 194 @Override 195 public void onPanelClosed(int action) { 196 setControlsEnabled(false); 197 if (mOptionsPanelViewType == LANGUAGE_PANEL) { 198 // Close the sub panel and show the infobar again. 199 mOptionsPanelViewType = NO_PANEL; 200 updateViewForCurrentState(createView()); 201 } else { 202 // Apply options and close the infobar. 203 onTranslateInfoBarButtonClicked(action); 204 } 205 } 206 207 private void onTranslateInfoBarButtonClicked(int action) { 208 onOptionsChanged(); 209 210 // We need to re-check if the pointer is null now because applying options (like never 211 // translate this site) can sometimes trigger closing the InfoBar. 212 if (mNativeInfoBarPtr == 0) return; 213 nativeOnButtonClicked(mNativeInfoBarPtr, action, ""); 214 } 215 216 @Override 217 public void setControlsEnabled(boolean state) { 218 super.setControlsEnabled(state); 219 220 // Handle the "Always Translate" checkbox. 221 ContentWrapperView wrapper = getContentWrapper(false); 222 if (wrapper != null) { 223 CheckBox checkBox = (CheckBox) wrapper.findViewById(R.id.infobar_extra_check); 224 if (checkBox != null) checkBox.setEnabled(state); 225 } 226 } 227 228 @Override 229 public void onOptionsChanged() { 230 if (mNativeInfoBarPtr == 0) return; 231 232 if (mOptions.optionsChanged()) { 233 mTranslateDelegate.applyTranslateOptions(mNativeInfoBarPtr, 234 mOptions.sourceLanguageIndex(), 235 mOptions.targetLanguageIndex(), 236 mOptions.alwaysTranslateLanguageState(), 237 mOptions.neverTranslateLanguageState(), 238 mOptions.neverTranslateDomainState()); 239 } 240 } 241 242 private boolean needsNeverPanel() { 243 return (getInfoBarType() == TranslateInfoBar.BEFORE_TRANSLATE_INFOBAR 244 && mShouldShowNeverBar); 245 } 246 247 private boolean needsAlwaysPanel() { 248 return (getInfoBarType() == TranslateInfoBar.AFTER_TRANSLATE_INFOBAR 249 && mOptions.alwaysTranslateLanguageState() 250 && !DeviceFormFactor.isTablet(getContext())); 251 } 252 253 /** 254 * @param newPanel id of the new panel to swap in. Use NO_PANEL to 255 * simply remove the current panel. 256 */ 257 private void swapPanel(int newPanel) { 258 assert (newPanel >= NO_PANEL && newPanel < MAX_PANEL_INDEX); 259 mOptionsPanelViewType = newPanel; 260 updateViewForCurrentState(createView()); 261 } 262 263 /** 264 * @return a panel of the specified {@code type} 265 */ 266 private TranslateSubPanel panelFor(int type) { 267 assert (type >= NO_PANEL && type < MAX_PANEL_INDEX); 268 switch (type) { 269 case LANGUAGE_PANEL: 270 return new TranslateLanguagePanel(this, mOptions); 271 case NEVER_PANEL: 272 return new TranslateNeverPanel(this, mOptions); 273 case ALWAYS_PANEL: 274 return new TranslateAlwaysPanel(this, mOptions); 275 default: 276 return null; 277 } 278 } 279 280 /** 281 * Swaps out the current view in the ContentViewWrapper. 282 */ 283 private void updateViewForCurrentState(View replacement) { 284 setControlsEnabled(false); 285 getInfoBarContainer().swapInfoBarViews(this, replacement); 286 } 287 288 /** 289 * @return a formatted message with links to {@code panelId}. 290 */ 291 private CharSequence formatBeforeInfoBarMessage(String template, String sourceLanguage, 292 String targetLanguage, final int panelId) { 293 294 SpannableString formattedSourceLanguage = new SpannableString(sourceLanguage); 295 formattedSourceLanguage.setSpan(new ClickableSpan() { 296 @Override 297 public void onClick(View view) { 298 swapPanel(panelId); 299 } 300 }, 0, sourceLanguage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 301 302 SpannableString formattedTargetLanguage = new SpannableString(targetLanguage); 303 formattedTargetLanguage.setSpan(new ClickableSpan() { 304 @Override 305 public void onClick(View view) { 306 swapPanel(panelId); 307 } 308 }, 0, targetLanguage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 309 310 return TextUtils.expandTemplate(template, formattedSourceLanguage, formattedTargetLanguage); 311 } 312 313 /** 314 * @return a formatted message with a link to {@code panelId} 315 */ 316 private CharSequence formatAfterTranslateInfoBarMessage(String statement, String linkText, 317 final int panelId) { 318 SpannableStringBuilder result = new SpannableStringBuilder(); 319 result.append(statement).append(" "); 320 SpannableString formattedChange = new SpannableString(linkText); 321 formattedChange.setSpan(new ClickableSpan() { 322 @Override 323 public void onClick(View view) { 324 swapPanel(panelId); 325 } 326 }, 0, linkText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 327 result.append(formattedChange); 328 return result; 329 } 330 331 int getInfoBarType() { 332 return mInfoBarType; 333 } 334 335 void changeInfoBarTypeAndNativePointer(int infoBarType, long newNativePointer) { 336 if (infoBarType >= 0 && infoBarType < MAX_INFOBAR_INDEX) { 337 mInfoBarType = infoBarType; 338 replaceNativePointer(newNativePointer); 339 updateViewForCurrentState(createView()); 340 } else { 341 assert false : "Trying to change the InfoBar to a type that is invalid."; 342 } 343 } 344 } 345