Commit b6d04acd authored by newt@chromium.org's avatar newt@chromium.org

Visual makeover for infobars.

NOTRY=true
BUG=375379

Review URL: https://codereview.chromium.org/378043002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@282206 0039d316-1c4b-4281-b951-d872f2087c98
parent 749cbc62
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2014 The Chromium Authors. All rights reserved.
<!-- Copyright 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. found in the LICENSE file.
--> -->
...@@ -9,7 +8,16 @@ ...@@ -9,7 +8,16 @@
<item <item
android:state_pressed="false" android:state_pressed="false"
android:state_focused="false" android:state_focused="false"
android:drawable="@drawable/infobar_button_normal_floating_enabled" /> android:state_selected="false">
<item <shape>
android:drawable="@drawable/infobar_button_normal_floating_pressed" /> <solid android:color="@color/infobar_accent_blue" />
<corners android:radius="1dp" />
</shape>
</item>
<item>
<shape>
<solid android:color="@color/infobar_accent_blue_pressed" />
<corners android:radius="1dp" />
</shape>
</item>
</selector> </selector>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="false"
android:state_focused="false"
android:drawable="@drawable/infobar_button_normal_full_left_enabled" />
<item
android:drawable="@drawable/infobar_button_normal_full_left_pressed" />
</selector>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="false"
android:state_focused="false"
android:drawable="@drawable/infobar_button_normal_full_right_enabled" />
<item
android:drawable="@drawable/infobar_button_normal_full_right_pressed" />
</selector>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="false"
android:state_focused="false"
android:drawable="@android:color/transparent" />
<item
android:state_pressed="false"
android:state_focused="true"
android:drawable="@drawable/infobar_close_button_focused" />
<item
android:drawable="@drawable/infobar_close_button_pressed" />
</selector>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="1dp" />
<solid android:color="@color/button_focused" />
<stroke
android:width="1px"
android:color="#80000000" />
</shape>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="1dp" />
<solid android:color="@color/button_pressed" />
</shape>
...@@ -7,16 +7,13 @@ ...@@ -7,16 +7,13 @@
<Button xmlns:android="http://schemas.android.com/apk/res/android" <Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="60dp" android:background="?android:attr/selectableItemBackground"
android:minHeight="@dimen/infobar_touch_target_height" android:fontFamily="sans-serif-medium"
android:minWidth="88dp"
android:minHeight="36dp"
android:textSize="@dimen/infobar_button_text_size" android:textSize="@dimen/infobar_button_text_size"
android:textColor="@drawable/infobar_button_text"
android:shadowColor="#FFF"
android:shadowDx="1.0"
android:shadowDy="1.0"
android:shadowRadius="1.0"
android:background="@drawable/infobar_button_normal_floating"
android:paddingStart="@dimen/infobar_button_horizontal_padding" android:paddingStart="@dimen/infobar_button_horizontal_padding"
android:paddingEnd="@dimen/infobar_button_horizontal_padding" android:paddingEnd="@dimen/infobar_button_horizontal_padding"
android:paddingTop="5dp" android:paddingTop="5dp"
android:paddingBottom="5dp" /> android:paddingBottom="5dp"
android:textAllCaps="true" />
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
android:layout_marginBottom="@dimen/infobar_margin" android:layout_marginBottom="@dimen/infobar_margin"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.25"
android:textDirection="locale" android:textDirection="locale"
android:textSize="@dimen/infobar_text_size" android:textSize="@dimen/infobar_text_size"
android:textColor="@color/infobar_text" /> android:textColor="@color/infobar_text" />
...@@ -5,9 +5,13 @@ ...@@ -5,9 +5,13 @@
--> -->
<resources> <resources>
<!-- Infobar colors -->
<color name="infobar_text">#333</color> <color name="infobar_text">#333</color>
<color name="infobar_background">#fff</color> <color name="infobar_background">#fff</color>
<color name="infobar_background_separator">#afafaf</color> <color name="infobar_background_separator">#afafaf</color>
<color name="infobar_accent_blue">#4285f4</color>
<color name="infobar_accent_blue_pressed">#679df6</color>
<color name="infobar_tertiary_button_text">#969696</color>
<!-- Tab Switcher Colors --> <!-- Tab Switcher Colors -->
<color name="tab_switcher_background">#111111</color> <color name="tab_switcher_background">#111111</color>
...@@ -20,10 +24,6 @@ ...@@ -20,10 +24,6 @@
<color name="accessibility_tab_switcher_list_item">#252525</color> <color name="accessibility_tab_switcher_list_item">#252525</color>
<color name="accessibility_close_undo_text">#33b5e5</color> <color name="accessibility_close_undo_text">#33b5e5</color>
<!-- Button Colors -->
<color name="button_pressed">#ff77c5e1</color>
<color name="button_focused">#ffa9d0df</color>
<!-- App banner colors --> <!-- App banner colors -->
<color name="app_banner_install_button_fg">#ffffff</color> <color name="app_banner_install_button_fg">#ffffff</color>
<color name="app_banner_open_button_fg">#777777</color> <color name="app_banner_open_button_fg">#777777</color>
......
...@@ -31,16 +31,16 @@ ...@@ -31,16 +31,16 @@
<!-- Text size of the infobar message. --> <!-- Text size of the infobar message. -->
<dimen name="infobar_text_size">16sp</dimen> <dimen name="infobar_text_size">16sp</dimen>
<!-- Text size of text inside infobar buttons. --> <!-- Text size of text inside infobar buttons. -->
<dimen name="infobar_button_text_size">16sp</dimen> <dimen name="infobar_button_text_size">12sp</dimen>
<!-- Minimum dimension (height or width) of the upper row of an infobar. --> <!-- Minimum width of an infobar. -->
<dimen name="infobar_min_size">60dp</dimen> <dimen name="infobar_min_width">220dp</dimen>
<!-- Margin between items in an infobar. --> <!-- Margin between items in an infobar. -->
<dimen name="infobar_margin">10dp</dimen> <dimen name="infobar_margin">16dp</dimen>
<!-- Left/right padding for infobar button text. --> <!-- Left/right padding for infobar button text. -->
<dimen name="infobar_button_horizontal_padding">30dp</dimen> <dimen name="infobar_button_horizontal_padding">20dp</dimen>
<!-- Minimum height for a touch target. --> <!-- Minimum height for a touch target. -->
<dimen name="infobar_touch_target_height">40dp</dimen> <dimen name="infobar_touch_target_height">40dp</dimen>
<!-- Height and width of the infobar icon. --> <!-- Width and height of the infobar icon. -->
<dimen name="infobar_icon_size">36dp</dimen> <dimen name="infobar_icon_size">36dp</dimen>
<!-- App banner dimensions --> <!-- App banner dimensions -->
......
...@@ -9,5 +9,6 @@ ...@@ -9,5 +9,6 @@
<item type="id" name="infobar_close_button" /> <item type="id" name="infobar_close_button" />
<item type="id" name="button_primary" /> <item type="id" name="button_primary" />
<item type="id" name="button_secondary" /> <item type="id" name="button_secondary" />
<item type="id" name="button_tertiary" />
<item type="id" name="infobar_extra_check" /> <item type="id" name="infobar_extra_check" />
</resources> </resources>
...@@ -236,8 +236,7 @@ public class AnimationHelper implements ViewTreeObserver.OnGlobalLayoutListener ...@@ -236,8 +236,7 @@ public class AnimationHelper implements ViewTreeObserver.OnGlobalLayoutListener
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mToShow != null && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && mToShow != null &&
(mAnimationType == ANIMATION_TYPE_SHOW || (mAnimationType == ANIMATION_TYPE_SHOW ||
mAnimationType == ANIMATION_TYPE_SWAP)) { mAnimationType == ANIMATION_TYPE_SWAP)) {
mToShow.announceForAccessibility( mToShow.announceForAccessibility(mInfoBar.getMessage());
mInfoBar.getMessageText(mContainer.getContext()));
} }
} }
}); });
......
...@@ -4,92 +4,38 @@ ...@@ -4,92 +4,38 @@
package org.chromium.chrome.browser.infobar; package org.chromium.chrome.browser.infobar;
import android.content.Context;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.view.View;
/** /**
* An infobar that presents the user with 2 buttons (typically OK, Cancel). * An infobar that presents the user with several buttons.
*
* TODO(newt): merge this into InfoBar.java.
*/ */
public class ConfirmInfoBar extends TwoButtonInfoBar { public class ConfirmInfoBar extends InfoBar {
// Message to prompt the user. /** Text shown on the primary button, e.g. "OK". */
private final String mMessage;
// Link text shown to the user, in addition to the message.
private final String mLinkText;
// Typically set to "OK", or some other positive action.
private final String mPrimaryButtonText; private final String mPrimaryButtonText;
// Typically set to "Cancel", or some other negative action. /** Text shown on the secondary button, e.g. "Cancel".*/
private final String mSecondaryButtonText; private final String mSecondaryButtonText;
// Listens for when either of the buttons is clicked. /** Text shown on the extra button, e.g. "More info". */
private final InfoBarListeners.Confirm mConfirmListener; private final String mTertiaryButtonText;
public ConfirmInfoBar(InfoBarListeners.Confirm confirmListener, int backgroundType,
int iconDrawableId, String message, String primaryButtonText,
String secondaryButtonText) {
this(confirmListener, iconDrawableId, message, primaryButtonText, secondaryButtonText);
}
public ConfirmInfoBar(InfoBarListeners.Confirm confirmListener, int iconDrawableId, /** Notified when one of the buttons is clicked. */
String message, String primaryButtonText, String secondaryButtonText) { private final InfoBarListeners.Confirm mConfirmListener;
this(confirmListener, iconDrawableId, message, null, primaryButtonText,
secondaryButtonText);
}
public ConfirmInfoBar(InfoBarListeners.Confirm confirmListener, int iconDrawableId,
String message, String linkText, String primaryButtonText, String secondaryButtonText) {
this(0, confirmListener, iconDrawableId, message, linkText, primaryButtonText,
secondaryButtonText);
}
public ConfirmInfoBar(long nativeInfoBar, InfoBarListeners.Confirm confirmListener, public ConfirmInfoBar(long nativeInfoBar, InfoBarListeners.Confirm confirmListener,
int iconDrawableId, String message, String linkText, String primaryButtonText, int iconDrawableId, String message, String linkText, String primaryButtonText,
String secondaryButtonText) { String secondaryButtonText) {
super(confirmListener, iconDrawableId); super(confirmListener, iconDrawableId, message);
mMessage = message;
mLinkText = linkText;
mPrimaryButtonText = primaryButtonText; mPrimaryButtonText = primaryButtonText;
mSecondaryButtonText = secondaryButtonText; mSecondaryButtonText = secondaryButtonText;
mTertiaryButtonText = linkText;
mConfirmListener = confirmListener; mConfirmListener = confirmListener;
setNativeInfoBar(nativeInfoBar); setNativeInfoBar(nativeInfoBar);
} }
@Override @Override
public CharSequence getMessageText(Context context) { public void createContent(InfoBarLayout layout) {
// Construct text to be displayed on the infobar. layout.setButtons(mPrimaryButtonText, mSecondaryButtonText, mTertiaryButtonText);
SpannableStringBuilder infobarMessage = new SpannableStringBuilder(mMessage);
// If we have a link text to display, append it.
if (!TextUtils.isEmpty(mLinkText)) {
SpannableStringBuilder spannableLinkText = new SpannableStringBuilder(mLinkText);
ClickableSpan onLinkClicked = new ClickableSpan() {
@Override
public void onClick(View view) {
onLinkClicked();
}
};
spannableLinkText.setSpan(onLinkClicked, 0, spannableLinkText.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
infobarMessage.append(" ");
infobarMessage.append(spannableLinkText);
}
return infobarMessage;
}
@Override
public String getPrimaryButtonText(Context context) {
return mPrimaryButtonText;
}
@Override
public String getSecondaryButtonText(Context context) {
return mSecondaryButtonText;
} }
@Override @Override
......
...@@ -36,11 +36,8 @@ public abstract class InfoBar implements InfoBarView { ...@@ -36,11 +36,8 @@ public abstract class InfoBar implements InfoBarView {
public static final int ACTION_TYPE_TRANSLATE = 3; public static final int ACTION_TYPE_TRANSLATE = 3;
public static final int ACTION_TYPE_TRANSLATE_SHOW_ORIGINAL = 4; public static final int ACTION_TYPE_TRANSLATE_SHOW_ORIGINAL = 4;
// Background types
public static final int BACKGROUND_TYPE_INFO = 0;
public static final int BACKGROUND_TYPE_WARNING = 1;
private final int mIconDrawableId; private final int mIconDrawableId;
private final CharSequence mMessage;
private InfoBarListeners.Dismiss mListener; private InfoBarListeners.Dismiss mListener;
private ContentWrapperView mContentView; private ContentWrapperView mContentView;
...@@ -66,14 +63,23 @@ public abstract class InfoBar implements InfoBarView { ...@@ -66,14 +63,23 @@ public abstract class InfoBar implements InfoBarView {
/** /**
* @param listener Listens to when buttons have been clicked on the InfoBar. * @param listener Listens to when buttons have been clicked on the InfoBar.
* @param iconDrawableId ID of the resource to use for the Icon. If 0, no icon will be shown. * @param iconDrawableId ID of the resource to use for the Icon. If 0, no icon will be shown.
* @param message The message to show in the infobar.
*/ */
public InfoBar(InfoBarListeners.Dismiss listener, int iconDrawableId) { public InfoBar(InfoBarListeners.Dismiss listener, int iconDrawableId, CharSequence message) {
mListener = listener; mListener = listener;
mId = generateId(); mId = generateId();
mIconDrawableId = iconDrawableId; mIconDrawableId = iconDrawableId;
mMessage = message;
mExpireOnNavigation = true; mExpireOnNavigation = true;
} }
/**
* @return The message shown in the infobar, useful for accessibility.
*/
public CharSequence getMessage() {
return mMessage;
}
/** /**
* Stores a pointer to the native-side counterpart of this InfoBar. * Stores a pointer to the native-side counterpart of this InfoBar.
* @param nativeInfoBarPtr Pointer to the NativeInfoBar. * @param nativeInfoBarPtr Pointer to the NativeInfoBar.
...@@ -96,13 +102,13 @@ public abstract class InfoBar implements InfoBarView { ...@@ -96,13 +102,13 @@ public abstract class InfoBar implements InfoBarView {
} }
/** /**
* Determine if the infobar should be dismissed when |url| is loaded. Calling * Determine if the infobar should be dismissed when a new page starts loading. Calling
* setExpireOnNavigation(true/false) causes this method always to return true/false. * setExpireOnNavigation(true/false) causes this method always to return true/false.
* This only applies to java-only infobars. C++ infobars will use the same logic * This only applies to java-only infobars. C++ infobars will use the same logic
* as other platforms so they are not attempted to be dismissed twice. * as other platforms so they are not attempted to be dismissed twice.
* It should really be removed once all infobars have a C++ counterpart. * It should really be removed once all infobars have a C++ counterpart.
*/ */
public final boolean shouldExpire(String url) { public final boolean shouldExpire() {
return mExpireOnNavigation && mNativeInfoBarPtr == 0; return mExpireOnNavigation && mNativeInfoBarPtr == 0;
} }
...@@ -146,7 +152,10 @@ public abstract class InfoBar implements InfoBarView { ...@@ -146,7 +152,10 @@ public abstract class InfoBar implements InfoBarView {
*/ */
protected final View createView() { protected final View createView() {
assert mContext != null; assert mContext != null;
return new InfoBarLayout(mContext, this, mIconDrawableId);
InfoBarLayout layout = new InfoBarLayout(mContext, this, mIconDrawableId, mMessage);
createContent(layout);
return layout;
} }
/** /**
...@@ -207,10 +216,16 @@ public abstract class InfoBar implements InfoBarView { ...@@ -207,10 +216,16 @@ public abstract class InfoBar implements InfoBarView {
public void setControlsEnabled(boolean state) { public void setControlsEnabled(boolean state) {
mControlsEnabled = state; mControlsEnabled = state;
// Handle the close button. // Disable all buttons on the infobar.
if (mContentView != null) { if (mContentView != null) {
View closeButton = mContentView.findViewById(R.id.infobar_close_button); View closeButton = mContentView.findViewById(R.id.infobar_close_button);
View primaryButton = mContentView.findViewById(R.id.button_primary);
View secondaryButton = mContentView.findViewById(R.id.button_secondary);
View tertiaryButton = mContentView.findViewById(R.id.button_tertiary);
if (closeButton != null) closeButton.setEnabled(state); if (closeButton != null) closeButton.setEnabled(state);
if (primaryButton != null) primaryButton.setEnabled(state);
if (secondaryButton != null) secondaryButton.setEnabled(state);
if (tertiaryButton != null) tertiaryButton.setEnabled(state);
} }
} }
...@@ -227,16 +242,6 @@ public abstract class InfoBar implements InfoBarView { ...@@ -227,16 +242,6 @@ public abstract class InfoBar implements InfoBarView {
public void createContent(InfoBarLayout layout) { public void createContent(InfoBarLayout layout) {
} }
@Override
public String getPrimaryButtonText(Context context) {
return null;
}
@Override
public String getSecondaryButtonText(Context context) {
return null;
}
/** /**
* Returns the id of the tab this infobar is showing into. * Returns the id of the tab this infobar is showing into.
*/ */
......
...@@ -369,11 +369,11 @@ public class InfoBarContainer extends LinearLayout { ...@@ -369,11 +369,11 @@ public class InfoBarContainer extends LinearLayout {
} }
// Called by the tab when it has started loading a new page. // Called by the tab when it has started loading a new page.
public void onPageStarted(String url) { public void onPageStarted() {
LinkedList<InfoBar> barsToRemove = new LinkedList<InfoBar>(); LinkedList<InfoBar> barsToRemove = new LinkedList<InfoBar>();
for (InfoBar infoBar : mInfoBars) { for (InfoBar infoBar : mInfoBars) {
if (infoBar.shouldExpire(url)) { if (infoBar.shouldExpire()) {
barsToRemove.add(infoBar); barsToRemove.add(infoBar);
} }
} }
......
...@@ -5,8 +5,13 @@ ...@@ -5,8 +5,13 @@
package org.chromium.chrome.browser.infobar; package org.chromium.chrome.browser.infobar;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
...@@ -15,732 +20,525 @@ import android.widget.ImageButton; ...@@ -15,732 +20,525 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.ui.base.LocalizationUtils;
import java.util.ArrayList;
/** /**
* Layout that can be used to arrange an InfoBar's View. * Layout that arranges an InfoBar's views. An InfoBarLayout consists of:
* All InfoBars consist of at least:
* - An icon representing the InfoBar's purpose on the left side.
* - A message describing the action that the user can take. * - A message describing the action that the user can take.
* - A close button on the right side. * - A close button on the right side.
* - (optional) An icon representing the infobar's purpose on the left side.
* - (optional) Additional "custom" views (e.g. a checkbox and text, or a pair of spinners)
* - (optional) One or two buttons with text at the bottom.
* *
* Views should never be added with anything but a call to addGroup() to ensure that groups are not * When adding custom views, widths and heights defined in the LayoutParams will be ignored.
* broken apart. * However, setting a minimum width in another way, like TextView.getMinWidth(), should still be
* * obeyed.
* Widths and heights defined in the LayoutParams will be overwritten due to the nature of the
* layout algorithm. However, setting a minimum width in another way, like TextView.getMinWidth(),
* should still be obeyed.
* *
* Logic for what happens when things are clicked should be implemented by the InfoBarView. * Logic for what happens when things are clicked should be implemented by the InfoBarView.
*/ */
public class InfoBarLayout extends ViewGroup implements View.OnClickListener { public class InfoBarLayout extends ViewGroup implements View.OnClickListener {
private static final String TAG = "InfoBarLayout";
/** /**
* Parameters used for laying out children. * Parameters used for laying out children.
*/ */
public static class LayoutParams extends ViewGroup.LayoutParams { private static class LayoutParams extends ViewGroup.LayoutParams {
/** Alignment parameters that determine where in the main row an item will float. */
public static final int ALIGN_START = 0;
public static final int ALIGN_END = 1;
/** Whether the View is meant for the main row. */ public int startMargin;
public boolean isInMainRow; public int endMargin;
public int topMargin;
public int bottomMargin;
/** Views grouped together are laid out together immediately adjacent to each other. */ // Where this view will be laid out. These values are assigned in onMeasure() and used in
public boolean isGroupedWithNextView; // onLayout().
public int start;
public int top;
/** When on the main row, indicates whether the control floats on the left or the right. */ LayoutParams(int startMargin, int topMargin, int endMargin, int bottomMargin) {
public int align; super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
this.startMargin = startMargin;
this.topMargin = topMargin;
this.endMargin = endMargin;
this.bottomMargin = bottomMargin;
}
}
/** If the control is a button, ID of the resource that was last used as its background. */ private static class Group {
public int background; public View[] views;
/**
* The gravity of each view in Group. Must be either Gravity.START, Gravity.END, or
* Gravity.FILL_HORIZONTAL.
*/
public int gravity = Gravity.START;
/** Whether the views are vertically stacked. */
public boolean isStacked;
void setHorizontalMode(int horizontalSpacing, int startMargin, int endMargin) {
isStacked = false;
for (int i = 0; i < views.length; i++) {
LayoutParams lp = (LayoutParams) views[i].getLayoutParams();
lp.startMargin = i == 0 ? startMargin : horizontalSpacing;
lp.topMargin = 0;
lp.endMargin = i == views.length - 1 ? endMargin : 0;
lp.bottomMargin = 0;
}
public LayoutParams() {
super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
align = ALIGN_END;
isInMainRow = true;
} }
public LayoutParams(LayoutParams other) { void setVerticalMode(int verticalSpacing, int bottomMargin) {
super(other); isStacked = true;
isGroupedWithNextView = other.isGroupedWithNextView; for (int i = 0; i < views.length; i++) {
align = other.align; LayoutParams lp = (LayoutParams) views[i].getLayoutParams();
isInMainRow = other.isInMainRow; lp.startMargin = 0;
lp.topMargin = i == 0 ? 0 : verticalSpacing;
lp.endMargin = 0;
lp.bottomMargin = i == views.length - 1 ? bottomMargin : 0;
}
} }
} }
private static class GroupInfo { private static final int ROW_MAIN = 1;
public int numViews; private static final int ROW_OTHER = 2;
public int width;
public int greatestMemberWidth;
public int endIndex;
public boolean hasButton;
};
private final int mDimensionMinSize;
private final int mDimensionMargin;
private final int mDimensionIconSize;
private final boolean mLayoutRTL;
private final InfoBarView mInfoBarView;
private final ImageView mIconView; private final int mMargin;
private final int mIconSize;
private final int mMinWidth;
private final int mAccentColor;
private final InfoBarView mInfoBarView;
private final TextView mMessageView; private final TextView mMessageView;
private final ImageButton mCloseButton; private final ImageButton mCloseButton;
private ImageView mIconView;
/** Background resource IDs to use for the buttons. */ private Group mMainGroup;
private final int mBackgroundFloating; private Group mCustomGroup;
private final int mBackgroundFullLeft; private Group mButtonGroup;
private final int mBackgroundFullRight;
/** /**
* Indices of child Views that start new layout rows. * These values are used during onMeasure() to track where the next view will be placed.
* The last entry is the number of child Views, allowing calculation of the size of each row by *
* taking the difference between subsequent indices. * mWidth is the infobar width.
* [mStart, mEnd) is the range of unoccupied space on the current row.
* mTop and mBottom are the top and bottom of the current row.
*
* These values, along with a view's gravity, are used to position the next view.
* These values are updated after placing a view and after starting a new row.
*/ */
private final ArrayList<Integer> mIndicesOfRows; private int mWidth;
private int mStart;
private int mEnd;
private int mTop;
private int mBottom;
/** /**
* Constructs the layout for the specified InfoBar. * Constructs a layout for the specified InfoBar. After calling this, be sure to set the
* message, the buttons, and/or the custom content using setMessage(), setButtons(), and
* setCustomContent().
*
* @param context The context used to render. * @param context The context used to render.
* @param infoBarView InfoBarView that listens to events. * @param infoBarView InfoBarView that listens to events.
* @param iconResourceId ID of the icon to use for the InfoBar. * @param iconResourceId ID of the icon to use for the InfoBar.
* @param message The message to show in the infobar.
*/ */
public InfoBarLayout(Context context, InfoBarView infoBarView, int iconResourceId) { public InfoBarLayout(Context context, InfoBarView infoBarView, int iconResourceId,
CharSequence message) {
super(context); super(context);
mIndicesOfRows = new ArrayList<Integer>();
mLayoutRTL = LocalizationUtils.isLayoutRtl();
mInfoBarView = infoBarView; mInfoBarView = infoBarView;
// Determine what backgrounds we'll be needing for the buttons.
mBackgroundFloating = R.drawable.infobar_button_normal_floating;
mBackgroundFullLeft = R.drawable.infobar_button_normal_full_left;
mBackgroundFullRight = R.drawable.infobar_button_normal_full_right;
// Grab the dimensions. // Grab the dimensions.
mDimensionMinSize = Resources res = getResources();
context.getResources().getDimensionPixelSize(R.dimen.infobar_min_size); mMargin = res.getDimensionPixelOffset(R.dimen.infobar_margin);
mDimensionMargin = mIconSize = res.getDimensionPixelSize(R.dimen.infobar_icon_size);
context.getResources().getDimensionPixelSize(R.dimen.infobar_margin); mMinWidth = res.getDimensionPixelSize(R.dimen.infobar_min_width);
mDimensionIconSize = mAccentColor = res.getColor(R.color.infobar_accent_blue);
context.getResources().getDimensionPixelSize(R.dimen.infobar_icon_size);
// Create the main controls.
mCloseButton = new ImageButton(context);
mIconView = new ImageView(context);
mMessageView = (TextView) LayoutInflater.from(context).inflate(R.layout.infobar_text, null);
addGroup(mCloseButton, mIconView, mMessageView);
// Set up the close button. // Set up the close button. Apply padding so it has a big touch target.
mCloseButton = new ImageButton(context);
mCloseButton.setId(R.id.infobar_close_button); mCloseButton.setId(R.id.infobar_close_button);
mCloseButton.setImageResource(R.drawable.infobar_close_button); mCloseButton.setImageResource(R.drawable.infobar_close_button);
mCloseButton.setBackgroundResource(R.drawable.infobar_close_bg); TypedArray a = getContext().obtainStyledAttributes(
new int [] {android.R.attr.selectableItemBackground});
Drawable closeButtonBackground = a.getDrawable(0);
a.recycle();
ApiCompatibilityUtils.setBackgroundForView(mCloseButton, closeButtonBackground);
mCloseButton.setPadding(mMargin, mMargin, mMargin, mMargin);
mCloseButton.setOnClickListener(this); mCloseButton.setOnClickListener(this);
mCloseButton.setContentDescription(res.getString(R.string.infobar_close));
mCloseButton.setContentDescription(getResources().getString(R.string.infobar_close)); mCloseButton.setLayoutParams(new LayoutParams(0, -mMargin, -mMargin, -mMargin));
addView(mCloseButton);
// Set up the icon. // Set up the icon.
mIconView.setFocusable(false);
if (iconResourceId != 0) { if (iconResourceId != 0) {
mIconView = new ImageView(context);
mIconView.setImageResource(iconResourceId); mIconView.setImageResource(iconResourceId);
} else { mIconView.setFocusable(false);
mIconView.setVisibility(View.INVISIBLE); mIconView.setLayoutParams(new LayoutParams(0, 0, mMargin / 2, 0));
mIconView.getLayoutParams().width = mIconSize;
mIconView.getLayoutParams().height = mIconSize;
} }
// Set up the TextView. // Set up the message view.
mMessageView = (TextView) LayoutInflater.from(context).inflate(R.layout.infobar_text, null);
mMessageView.setText(message, TextView.BufferType.SPANNABLE);
mMessageView.setMovementMethod(LinkMovementMethod.getInstance()); mMessageView.setMovementMethod(LinkMovementMethod.getInstance());
mMessageView.setText(infoBarView.getMessageText(context), TextView.BufferType.SPANNABLE); mMessageView.setLinkTextColor(mAccentColor);
mMessageView.setLayoutParams(new LayoutParams(0, mMargin / 4, 0, 0));
// Only the close button floats to the right; the icon and the message both float left.
((LayoutParams) mIconView.getLayoutParams()).align = LayoutParams.ALIGN_START; if (mIconView != null) {
((LayoutParams) mMessageView.getLayoutParams()).align = LayoutParams.ALIGN_START; mMainGroup = addGroup(mIconView, mMessageView);
// Vertically center the icon and close buttons of an unstretched InfoBar. If the InfoBar
// is stretched, they both stay in place.
mIconView.getLayoutParams().width = mDimensionIconSize;
mIconView.getLayoutParams().height = mDimensionIconSize;
// We apply padding to the close button so that it has a big touch target.
int closeButtonHeight = mCloseButton.getDrawable().getIntrinsicHeight();
int closePadding = (mDimensionMinSize - closeButtonHeight) / 2;
if (closePadding >= 0) {
mCloseButton.setPadding(closePadding, closePadding, closePadding, closePadding);
} else { } else {
assert closePadding >= 0 : "Assets are too large for this layout."; mMainGroup = addGroup(mMessageView);
} }
// Add all of the other InfoBar specific controls.
infoBarView.createContent(this);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
} }
/** /**
* Add a view to the Layout. * Sets the message to show on the infobar.
* This function must never be called with an index that isn't -1 to ensure that groups aren't
* broken apart.
*/ */
@Override public void setMessage(CharSequence message) {
public void addView(View child, int index, ViewGroup.LayoutParams params) { mMessageView.setText(message, TextView.BufferType.SPANNABLE);
if (index == -1) {
super.addView(child, index, params);
} else {
assert false : "Adding children at random places can break group structure.";
super.addView(child, -1, params);
}
} }
/** /**
* Add a group of Views that are measured and laid out together. * Sets the custom content of the infobar. These views will be displayed in addition to the
* standard infobar controls (icon, text, buttons). Depending on the available space, view1 and
* view2 will be laid out:
* - Side by side on the main row,
* - Side by side on a separate row, each taking up half the width of the infobar,
* - Stacked above each other on two separate rows, taking up the full width of the infobar.
*/ */
public void addGroup(View... group) { public void setCustomContent(View view1, View view2) {
for (int i = 0; i < group.length; i++) { mCustomGroup = addGroup(view1, view2);
final View member = group[i];
addView(member);
LayoutParams params = (LayoutParams) member.getLayoutParams();
params.isGroupedWithNextView = (i != group.length - 1);
}
} }
/** /**
* Add up to two buttons to the layout. * Sets the custom content of the infobar to a single view. This view will be displayed in
* * addition to the standard infobar controls. Depending on the available space, the view will be
* Buttons with null text are hidden from view. The secondary button may only exist if the * displayed:
* primary button does. * - On the main row, start-aligned or end-aligned depending on whether there are also
* * buttons on the main row, OR
* @param primaryText Text for the primary button. * - On a separate row, start-aligned
* @param secondaryText Text for the secondary button.
*/ */
public void addButtons(String primaryText, String secondaryText) { public void setCustomContent(View view) {
Button primaryButton = null; mCustomGroup = addGroup(view);
Button secondaryButton = null;
if (!TextUtils.isEmpty(secondaryText)) {
secondaryButton = (Button) LayoutInflater.from(getContext()).inflate(
R.layout.infobar_button, null);
secondaryButton.setId(R.id.button_secondary);
secondaryButton.setOnClickListener(this);
secondaryButton.setText(secondaryText);
}
if (!TextUtils.isEmpty(primaryText)) {
primaryButton = (Button) LayoutInflater.from(getContext()).inflate(
R.layout.infobar_button, null);
primaryButton.setId(R.id.button_primary);
primaryButton.setOnClickListener(this);
primaryButton.setText(primaryText);
}
// Group the buttons together so that they are laid out next to each other.
if (primaryButton == null && secondaryButton != null) {
assert false : "When using only one button, make it the primary button.";
} else if (primaryButton != null && secondaryButton != null) {
addGroup(secondaryButton, primaryButton);
} else if (primaryButton != null) {
addGroup(primaryButton);
}
} }
@Override /**
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { * Calls setButtons(primaryText, secondaryText, null).
final int rowWidth = right - left; */
int rowTop = layoutMainRow(rowWidth); public void setButtons(String primaryText, String secondaryText) {
for (int row = 1; row < mIndicesOfRows.size() - 1; row++) { setButtons(primaryText, secondaryText, null);
rowTop = layoutRow(row, rowTop, rowWidth);
}
} }
/** /**
* Lays out the controls in the main row. * Adds one, two, or three buttons to the layout.
*
* This method is complicated mainly because of the arbitrariness for when a control can
* float either left or right, and whether we're doing an RTL layout.
* *
* Layout proceeds in three phases: * @param primaryText Text for the primary button.
* - Laying out of the icon and close button are done separately from the rest of the controls * @param secondaryText Text for the secondary button, or null if there isn't a second button.
* because they are locked into their respective corners. These two controls bound the rest * @param tertiaryText Text for the tertiary button, or null if there isn't a third button.
* of the controls in the main row.
*
* - Items floating to the left are then laid out, traversing the children array in a forwards
* manner. This includes the InfoBar message.
*
* - A final pass lays out items aligned to the end of the bar, traversing the children array
* backwards so that the correct ordering of the children is preserved. Going forwards would
* cause buttons to flip (e.g.).
*
* @param width Maximum width of the row.
* @return How tall the main row is.
*/ */
private int layoutMainRow(int width) { public void setButtons(String primaryText, String secondaryText, String tertiaryText) {
final int rowStart = mIndicesOfRows.get(0); if (TextUtils.isEmpty(primaryText)) return;
final int rowEnd = mIndicesOfRows.get(1);
final int rowHeight = computeMainRowHeight(rowStart, rowEnd); LayoutInflater inflater = LayoutInflater.from(getContext());
Button primaryButton = (Button) inflater.inflate(R.layout.infobar_button, null);
// Lay out the icon and the close button. primaryButton.setId(R.id.button_primary);
int closeLeft; primaryButton.setOnClickListener(this);
int iconPadding = (mDimensionMinSize - mDimensionIconSize) / 2; primaryButton.setText(primaryText);
int iconLeft = iconPadding; primaryButton.setBackgroundResource(R.drawable.btn_infobar_blue);
if (mLayoutRTL) { primaryButton.setTextColor(Color.WHITE);
iconLeft += width - mDimensionMinSize;
closeLeft = 0; if (TextUtils.isEmpty(secondaryText)) {
} else { mButtonGroup = addGroup(primaryButton);
closeLeft = width - mCloseButton.getMeasuredWidth(); return;
} }
mIconView.layout(iconLeft, iconPadding, iconLeft + mDimensionIconSize,
iconPadding + mDimensionIconSize);
mCloseButton.layout(closeLeft, 0, closeLeft + mDimensionMinSize, mDimensionMinSize);
// Go from left to right to catch all items aligned with the start of the InfoBar.
int rowLeft = mDimensionMinSize;
int rowRight = width - mDimensionMinSize;
for (int i = rowStart; i < rowEnd; i++) {
final View child = getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
if (params.align != LayoutParams.ALIGN_START || child.getVisibility() == View.GONE
|| child == mCloseButton || child == mIconView) {
continue;
}
// Everything is vertically centered. Button secondaryButton = (Button) inflater.inflate(R.layout.infobar_button, null);
int childTop = (rowHeight - child.getMeasuredHeight()) / 2; secondaryButton.setId(R.id.button_secondary);
int childLeft; secondaryButton.setOnClickListener(this);
secondaryButton.setText(secondaryText);
if (mLayoutRTL) { secondaryButton.setTextColor(mAccentColor);
if (!isMainControl(child)) rowRight -= mDimensionMargin;
childLeft = rowRight - child.getMeasuredWidth();
rowRight -= child.getMeasuredWidth();
} else {
if (!isMainControl(child)) rowLeft += mDimensionMargin;
childLeft = rowLeft;
rowLeft += child.getMeasuredWidth();
}
child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), if (TextUtils.isEmpty(tertiaryText)) {
childTop + child.getMeasuredHeight()); mButtonGroup = addGroup(secondaryButton, primaryButton);
return;
} }
// Go from right to left to catch all items aligned with the end of the InfoBar. Button tertiaryButton = (Button) inflater.inflate(R.layout.infobar_button, null);
for (int i = rowEnd - 1; i >= rowStart; i--) { tertiaryButton.setId(R.id.button_tertiary);
final View child = getChildAt(i); tertiaryButton.setOnClickListener(this);
LayoutParams params = (LayoutParams) child.getLayoutParams(); tertiaryButton.setText(tertiaryText);
if (params.align != LayoutParams.ALIGN_END || child.getVisibility() == View.GONE tertiaryButton.setPadding(mMargin / 2, tertiaryButton.getPaddingTop(), mMargin / 2,
|| child == mCloseButton || child == mIconView) { tertiaryButton.getPaddingBottom());
continue; tertiaryButton.setTextColor(
} getContext().getResources().getColor(R.color.infobar_tertiary_button_text));
// Everything is vertically centered.
int childTop = (rowHeight - child.getMeasuredHeight()) / 2;
int childLeft;
if (!mLayoutRTL) {
childLeft = rowRight - child.getMeasuredWidth();
rowRight -= child.getMeasuredWidth();
if (!isMainControl(child)) rowRight -= mDimensionMargin;
} else {
childLeft = rowLeft;
rowLeft += child.getMeasuredWidth();
if (!isMainControl(child)) rowLeft += mDimensionMargin;
}
child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
return rowHeight; mButtonGroup = addGroup(tertiaryButton, secondaryButton, primaryButton);
} }
/** /**
* Lays out the controls in the row other than the main one. * Adds a group of Views that are measured and laid out together.
*
* This case is much simpler than the main row since the items are all equally sized and simply
* entails moving through the children and laying them down from the start of the InfoBar to the
* end.
*
* @param row Index of the row
* @param rowTop Y-coordinate of the layout the controls should be aligned to.
* @param width Maximum width of the row.
* @return How tall the row is.
*/ */
private int layoutRow(int row, int rowTop, int width) { private Group addGroup(View... views) {
final int rowStart = mIndicesOfRows.get(row); Group group = new Group();
final int rowEnd = mIndicesOfRows.get(row + 1); group.views = views;
final boolean hasButton = isButton(getChildAt(rowStart));
int rowLeft = hasButton ? 0 : mDimensionMargin;
int rowRight = width - (hasButton ? 0 : mDimensionMargin);
for (int i = rowStart; i < rowEnd; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.GONE) continue;
int childLeft;
if (mLayoutRTL) {
childLeft = rowRight - child.getMeasuredWidth();
rowRight -= child.getMeasuredWidth() + (hasButton ? 0 : mDimensionMargin);
} else {
childLeft = rowLeft;
rowLeft += child.getMeasuredWidth() + (hasButton ? 0 : mDimensionMargin);
}
child.layout(childLeft, rowTop, childLeft + child.getMeasuredWidth(), for (View v : views) {
rowTop + child.getMeasuredHeight()); addView(v);
} }
return group;
return rowTop + computeRowHeight(rowStart, rowEnd);
} }
/** @Override
* Checks if the child is one of the main InfoBar controls. protected LayoutParams generateDefaultLayoutParams() {
* @param child View to check. return new LayoutParams(0, 0, 0, 0);
* @return True if the child is one of the main controls.
*/
private boolean isMainControl(View child) {
return child == mIconView || child == mMessageView || child == mCloseButton;
} }
/** @Override
* Marks that the given index is the start of its own row. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
* @param rowStartIndex Index of the child view at the start of the next row. // Place all the views in the positions already determined during onMeasure().
*/ int width = right - left;
private void addRowStartIndex(int rowStartIndex) { boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
if (mIndicesOfRows.size() == 0
|| rowStartIndex != mIndicesOfRows.get(mIndicesOfRows.size() - 1)) {
mIndicesOfRows.add(rowStartIndex);
}
}
/** for (int i = 0; i < getChildCount(); i++) {
* Computes properties of the next group of Views to assign to rows. View child = getChildAt(i);
* @param startIndex Index of the first child in the group. LayoutParams lp = (LayoutParams) child.getLayoutParams();
* @return GroupInfo containing information about the current group. int childLeft = lp.start;
*/ int childRight = lp.start + child.getMeasuredWidth();
private GroupInfo getNextGroup(int startIndex) {
GroupInfo groupInfo = new GroupInfo(); if (isRtl) {
groupInfo.endIndex = startIndex; int tmp = width - childRight;
childRight = width - childLeft;
final int childCount = getChildCount(); childLeft = tmp;
int currentChildIndex = startIndex;
while (groupInfo.endIndex < childCount) {
final View groupChild = getChildAt(groupInfo.endIndex);
if (groupChild.getVisibility() != View.GONE) {
groupInfo.hasButton |= isButton(groupChild);
groupInfo.width += groupChild.getMeasuredWidth();
groupInfo.greatestMemberWidth =
Math.max(groupInfo.greatestMemberWidth, groupChild.getMeasuredWidth());
groupInfo.numViews++;
} }
groupInfo.endIndex++;
LayoutParams params = (LayoutParams) groupChild.getLayoutParams(); child.layout(childLeft, lp.top, childRight, lp.top + child.getMeasuredHeight());
if (!params.isGroupedWithNextView) break;
} }
return groupInfo;
}
@Override
protected void measureChild(View child, int widthSpec, int heightSpec) {
// If a control is on the main row, then it should be only as large as it wants to be.
// Otherwise, it must occupy the same amount of space as everything else on its row.
LayoutParams params = (LayoutParams) child.getLayoutParams();
params.width = params.isInMainRow ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
super.measureChild(child, widthSpec, heightSpec);
} }
/**
* Measures *and* assigns positions to all of the views in the infobar. These positions are
* saved in each view's LayoutParams (lp.start and lp.top) and used during onLayout(). All of
* the interesting logic happens inside onMeasure(); onLayout() just assigns the already-
* determined positions and mirrors everything for RTL, if needed.
*/
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
assert getLayoutParams().height == LayoutParams.WRAP_CONTENT assert getLayoutParams().height == LayoutParams.WRAP_CONTENT
: "InfoBar heights cannot be constrained."; : "InfoBar heights cannot be constrained.";
final int maxWidth = MeasureSpec.getSize(widthMeasureSpec); // Measure all children without imposing any size constraints on them. This determines how
mIndicesOfRows.clear(); // big each child wants to be.
int unspecifiedSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
// Measure all children with the assumption that they may take up the full size of the for (int i = 0; i < getChildCount(); i++) {
// parent. This determines how big each child wants to be. measureChild(getChildAt(i), unspecifiedSpec, unspecifiedSpec);
final int childCount = getChildCount();
for (int numChild = 0; numChild < childCount; numChild++) {
final View child = getChildAt(numChild);
if (child.getVisibility() == View.GONE) continue;
((LayoutParams) child.getLayoutParams()).isInMainRow = true;
measureChild(child, widthMeasureSpec, heightMeasureSpec);
} }
// Allocate as many Views as possible to the main row, then place everything else on the // Avoid overlapping views, division by zero, infinite heights, and other fun problems that
// following rows. // could arise with extremely narrow infobars.
int currentChildIndex = measureMainRow(maxWidth); mWidth = Math.max(MeasureSpec.getSize(widthMeasureSpec), mMinWidth);
measureRemainingRows(maxWidth, currentChildIndex); mTop = mBottom = 0;
placeGroups();
// Buttons must have their backgrounds manually changed to give the illusion of having a
// single pixel boundary between them.
updateBackgroundsForButtons();
// Determine how tall the container should be by measuring all the children in their rows. setMeasuredDimension(mWidth, resolveSize(mBottom, heightMeasureSpec));
int layoutHeight = computeHeight();
setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
resolveSize(layoutHeight, heightMeasureSpec));
} }
/** /**
* Assign as many Views as can fit onto the main row. * Assigns positions to all of the views in the infobar. The icon, text, and close button are
* * placed on the main row. The custom content and finally the buttons are placed on the main row
* The main row consists of at least the icon, the close button, and the message. Groups of * if they fit. Otherwise, they go on their own rows.
* controls are added to the main row as long as they can fit within the width of the InfoBar.
*
* @param maxWidth The maximum width of the main row.
* @return The index of the last child that couldn't fit on the main row.
*/ */
private int measureMainRow(int maxWidth) { private void placeGroups() {
final int childCount = getChildCount(); startRow();
placeChild(mCloseButton, Gravity.END);
// The main row has the icon and the close button taking the upper left and upper right placeGroup(mMainGroup);
// corners of the InfoBar, each of which occupies a square of
// mDimensionMinSize x mDimensionMinSize pixels. int customGroupWidth = 0;
GroupInfo mainControlInfo = getNextGroup(0); if (mCustomGroup != null) {
int remainingWidth = maxWidth - (mDimensionMinSize * 2) - mMessageView.getMeasuredWidth(); updateCustomGroupForRow(ROW_MAIN);
addRowStartIndex(0); customGroupWidth = getWidthWithMargins(mCustomGroup);
}
// Go through the rest of the Views and keep adding them until they can't fit.
int currentChildIndex = mainControlInfo.endIndex; int buttonGroupWidth = 0;
while (currentChildIndex < childCount && remainingWidth > 0) { if (mButtonGroup != null) {
GroupInfo groupInfo = getNextGroup(currentChildIndex); updateButtonGroupForRow(ROW_MAIN);
int widthWithMargins = groupInfo.width + mDimensionMargin * groupInfo.numViews; buttonGroupWidth = getWidthWithMargins(mButtonGroup);
}
if (widthWithMargins <= remainingWidth) {
// If the group fits on the main row, add it. boolean customGroupOnMainRow = customGroupWidth <= availableWidth();
currentChildIndex = groupInfo.endIndex; boolean buttonGroupOnMainRow = customGroupWidth + buttonGroupWidth <= availableWidth();
remainingWidth -= widthWithMargins;
if (mCustomGroup != null) {
if (customGroupOnMainRow) {
mCustomGroup.gravity = (mButtonGroup != null && buttonGroupOnMainRow)
? Gravity.START : Gravity.END;
} else { } else {
// We can't fit the current group on the main row. startRow();
break; updateCustomGroupForRow(ROW_OTHER);
} }
placeGroup(mCustomGroup);
} }
addRowStartIndex(currentChildIndex);
if (mButtonGroup != null) {
// The icon and the close button are set to be squares occupying the upper left and if (!buttonGroupOnMainRow) {
// upper right corners of the InfoBar. startRow();
int specWidth = MeasureSpec.makeMeasureSpec(mDimensionMinSize, MeasureSpec.EXACTLY); updateButtonGroupForRow(ROW_OTHER);
int specHeight = MeasureSpec.makeMeasureSpec(mDimensionMinSize, MeasureSpec.EXACTLY);
measureChild(mIconView, specWidth, specHeight); // If the infobar consists of just a main row and a buttons row, the buttons must be
measureChild(mCloseButton, specWidth, specHeight); // at least 32dp below the bottom of the message text.
if (mCustomGroup == null) {
// Measure out everything else except the message. LayoutParams lp = (LayoutParams) mMessageView.getLayoutParams();
remainingWidth = maxWidth - (mDimensionMinSize * 2); int messageBottom = lp.top + mMessageView.getMeasuredHeight();
for (int i = 0; i < currentChildIndex; i++) { mTop = Math.max(mTop, messageBottom + 2 * mMargin);
final View child = getChildAt(i); }
if (child.getVisibility() == View.GONE || isMainControl(child)) continue; }
placeGroup(mButtonGroup);
specWidth = MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST);
specHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
measureChild(child, specWidth, specHeight);
remainingWidth -= child.getMeasuredWidth() + mDimensionMargin;
} }
// The message sucks up the remaining width on the line after all other controls startRow();
// have gotten all the space they requested.
specWidth = MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST);
specHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
measureChild(mMessageView, specWidth, specHeight);
return currentChildIndex; // If everything fits on a single row, center everything vertically.
if (buttonGroupOnMainRow) {
int layoutHeight = mBottom;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int extraSpace = layoutHeight - child.getMeasuredHeight();
LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.top = extraSpace / 2;
}
}
} }
/** /**
* Assign children to rows in the layout. * Places a group of views on the current row, or stacks them over multiple rows if
* * group.isStacked is true. mStart, mEnd, and mBottom are updated to reflect the space taken by
* We first try to assign children in the same group to the same row, but only if they fit when * the group.
* they are of equal width. Otherwise, we split the group onto multiple rows.
*
* @param maxWidth Maximum width that the row can take.
* @param currentChildIndex Start index of the current group.
*/ */
private void measureRemainingRows(int maxWidth, int currentChildIndex) { private void placeGroup(Group group) {
final int childCount = getChildCount(); if (group.gravity == Gravity.END) {
final int specHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); for (int i = group.views.length - 1; i >= 0; i--) {
placeChild(group.views[i], group.gravity);
while (currentChildIndex < childCount) { if (group.isStacked && i != 0) startRow();
GroupInfo groupInfo = getNextGroup(currentChildIndex);
int availableWidth;
int boundaryMargins;
if (groupInfo.hasButton) {
// Buttons take up the full width of the InfoBar.
availableWidth = maxWidth;
boundaryMargins = 0;
} else {
// Other controls obey the side boundaries, and have boundaries between them.
availableWidth = maxWidth - mDimensionMargin * 2;
boundaryMargins = (groupInfo.numViews - 1) * mDimensionMargin;
} }
} else { // group.gravity is Gravity.START or Gravity.FILL_HORIZONTAL
// Determine how wide each item would be on the same row, including boundaries. for (int i = 0; i < group.views.length; i++) {
int evenWidth = (availableWidth - boundaryMargins) / groupInfo.numViews; placeChild(group.views[i], group.gravity);
if (group.isStacked && i != group.views.length - 1) startRow();
if (groupInfo.greatestMemberWidth <= evenWidth) {
// Fit everything on the same row.
int specWidth = MeasureSpec.makeMeasureSpec(evenWidth, MeasureSpec.EXACTLY);
for (int i = currentChildIndex; i < groupInfo.endIndex; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.GONE) continue;
((LayoutParams) child.getLayoutParams()).isInMainRow = false;
measureChild(child, specWidth, specHeight);
}
addRowStartIndex(currentChildIndex);
} else {
// Add each member of the group to its own row.
int specWidth = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY);
for (int i = currentChildIndex; i < groupInfo.endIndex; i++) {
final View child = getChildAt(i);
if (child.getVisibility() == View.GONE) continue;
((LayoutParams) child.getLayoutParams()).isInMainRow = false;
measureChild(child, specWidth, specHeight);
addRowStartIndex(i);
}
} }
currentChildIndex = groupInfo.endIndex;
} }
addRowStartIndex(childCount);
} }
/** /**
* Calculate how tall the layout is, accounting for margins and children. * Places a single view on the current row, and updates the view's layout parameters to remember
* @return How big the layout should be. * its position. mStart, mEnd, and mBottom are updated to reflect the space taken by the view.
*/ */
private int computeHeight() { private void placeChild(View child, int gravity) {
int cumulativeHeight = 0; LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Calculate how big each row is. int availableWidth = Math.max(0, mEnd - mStart - lp.startMargin - lp.endMargin);
final int numRows = mIndicesOfRows.size() - 1; if (child.getMeasuredWidth() > availableWidth || gravity == Gravity.FILL_HORIZONTAL) {
for (int row = 0; row < numRows; row++) { measureChildWithFixedWidth(child, availableWidth);
final int rowStart = mIndicesOfRows.get(row); }
final int rowEnd = mIndicesOfRows.get(row + 1);
if (row == 0) { if (gravity == Gravity.START || gravity == Gravity.FILL_HORIZONTAL) {
cumulativeHeight += computeMainRowHeight(rowStart, rowEnd); lp.start = mStart + lp.startMargin;
} else { mStart = lp.start + child.getMeasuredWidth() + lp.endMargin;
cumulativeHeight += computeRowHeight(rowStart, rowEnd); } else { // gravity == Gravity.END
} lp.start = mEnd - lp.endMargin - child.getMeasuredWidth();
mEnd = lp.start - lp.startMargin;
} }
return cumulativeHeight; lp.top = mTop + lp.topMargin;
mBottom = Math.max(mBottom, lp.top + child.getMeasuredHeight() + lp.bottomMargin);
} }
/** /**
* Computes how tall the main row is. * Advances the current position to the next row and adds margins on the left, right, and top
* @param rowStart Index of the first child. * of the new row.
* @param rowEnd One past the index of the last child.
*/ */
private int computeMainRowHeight(int rowStart, int rowEnd) { private void startRow() {
// The icon and close button already have their margins baked into their padding values, mStart = mMargin;
// but the other Views have a margin above and below. mEnd = mWidth - mMargin;
final int verticalMargins = mDimensionMargin * 2; mTop = mBottom + mMargin;
int rowHeight = mDimensionMinSize; mBottom = mTop;
for (int i = rowStart; i < rowEnd; i++) { }
View child = getChildAt(i);
if (child == mCloseButton || child == mIconView || child.getVisibility() == View.GONE) { private int availableWidth() {
continue; return mEnd - mStart;
}
rowHeight = Math.max(rowHeight, child.getMeasuredHeight() + verticalMargins);
}
return rowHeight;
} }
/** /**
* Computes how tall a row below the main row is. * @return The width of the group, including the items' margins.
*
* Margins are only applied downward since the rows above are handling the margin on their side.
* Buttons ignore margins since they have to be right against the boundary.
*
* @param rowStart Index of the first child.
* @param rowEnd One past the index of the last child.
*/ */
private int computeRowHeight(int rowStart, int rowEnd) { private int getWidthWithMargins(Group group) {
boolean isButtonRow = isButton(getChildAt(rowStart)); if (group.isStacked) return getWidthWithMargins(group.views[0]);
final int verticalMargins = isButtonRow ? 0 : mDimensionMargin;
int rowHeight = 0; int width = 0;
for (int i = rowStart; i < rowEnd; i++) { for (View v : group.views) {
final View child = getChildAt(i); width += getWidthWithMargins(v);
if (child.getVisibility() == View.GONE) continue;
rowHeight = Math.max(rowHeight, child.getMeasuredHeight() + verticalMargins);
} }
return rowHeight; return width;
}
private int getWidthWithMargins(View child) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
return child.getMeasuredWidth() + lp.startMargin + lp.endMargin;
}
private void measureChildWithFixedWidth(View child, int width) {
int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(widthSpec, heightSpec);
} }
/** /**
* Determines if the given View is either the primary or secondary button. * The button group has different layout properties (margins, gravity, etc) when placed on the
* @param child View to check. * main row as opposed to on a separate row. This updates the layout properties of the button
* @return Whether the child is the primary or secondary button. * group to prepare for placing it on either the main row or a separate row.
*
* @param row One of ROW_MAIN or ROW_OTHER.
*/ */
private boolean isButton(View child) { private void updateButtonGroupForRow(int row) {
return child.getId() == R.id.button_secondary || child.getId() == R.id.button_primary; int startEndMargin = row == ROW_MAIN ? mMargin : 0;
mButtonGroup.setHorizontalMode(mMargin / 2, startEndMargin, startEndMargin);
mButtonGroup.gravity = Gravity.END;
if (row == ROW_OTHER && mButtonGroup.views.length >= 2) {
int extraWidth = availableWidth() - getWidthWithMargins(mButtonGroup);
if (extraWidth < 0) {
// Group is too wide to fit on a single row, so stack the group items vertically.
mButtonGroup.setVerticalMode(mMargin / 2, 0);
mButtonGroup.gravity = Gravity.FILL_HORIZONTAL;
} else if (mButtonGroup.views.length == 3) {
// Align tertiary button at the start and the other two buttons at the end.
((LayoutParams) mButtonGroup.views[0].getLayoutParams()).endMargin += extraWidth;
}
}
} }
/** /**
* Update the backgrounds for the buttons to account for their current positioning. * Analagous to updateButtonGroupForRow(), but for the custom group istead of the button group.
* The primary and secondary buttons are special-cased in that their backgrounds change to
* create the illusion of a single-stroke boundary between them.
*/ */
private void updateBackgroundsForButtons() { private void updateCustomGroupForRow(int row) {
boolean bothButtonsExist = findViewById(R.id.button_primary) != null int startEndMargin = row == ROW_MAIN ? mMargin : 0;
&& findViewById(R.id.button_secondary) != null; mCustomGroup.setHorizontalMode(mMargin, startEndMargin, startEndMargin);
mCustomGroup.gravity = Gravity.START;
for (int row = 0; row < mIndicesOfRows.size() - 1; row++) {
final int rowStart = mIndicesOfRows.get(row); if (row == ROW_OTHER && mCustomGroup.views.length == 2) {
final int rowEnd = mIndicesOfRows.get(row + 1); int extraWidth = availableWidth() - getWidthWithMargins(mCustomGroup);
final int rowSize = rowEnd - rowStart; if (extraWidth < 0) {
// Group is too wide to fit on a single row, so stack the group items vertically.
for (int i = rowStart; i < rowEnd; i++) { mCustomGroup.setVerticalMode(0, mMargin);
final View child = getChildAt(i); mCustomGroup.gravity = Gravity.FILL_HORIZONTAL;
if (child.getVisibility() == View.GONE || !isButton(child)) continue; } else {
// Expand the children to take up the entire row.
// Determine which background we need to show. View view0 = mCustomGroup.views[0];
int background; View view1 = mCustomGroup.views[1];
if (row == 0) { int extraWidth0 = extraWidth / 2;
// Button will be floating. int extraWidth1 = extraWidth - extraWidth0;
background = mBackgroundFloating; measureChildWithFixedWidth(view0, view0.getMeasuredWidth() + extraWidth0);
} else if (rowSize == 1 || !bothButtonsExist) { measureChildWithFixedWidth(view1, view1.getMeasuredWidth() + extraWidth1);
// Button takes up the full width of the screen.
background = mBackgroundFullRight;
} else if (mLayoutRTL) {
// Primary button will be to the left of the secondary.
background = child.getId() == R.id.button_primary
? mBackgroundFullLeft : mBackgroundFullRight;
} else {
// Primary button will be to the right of the secondary.
background = child.getId() == R.id.button_primary
? mBackgroundFullRight : mBackgroundFullLeft;
}
// Update the background.
LayoutParams params = (LayoutParams) child.getLayoutParams();
if (params.background != background) {
params.background = background;
// Save the padding; Android decides to overwrite it on some builds.
int paddingLeft = child.getPaddingLeft();
int paddingTop = child.getPaddingTop();
int paddingRight = child.getPaddingRight();
int paddingBottom = child.getPaddingBottom();
int buttonWidth = child.getMeasuredWidth();
int buttonHeight = child.getMeasuredHeight();
// Set the background, then restore the padding.
child.setBackgroundResource(background);
child.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
// Re-measuring is necessary to correct the text gravity.
int specWidth = MeasureSpec.makeMeasureSpec(buttonWidth, MeasureSpec.EXACTLY);
int specHeight = MeasureSpec.makeMeasureSpec(buttonHeight, MeasureSpec.EXACTLY);
measureChild(child, specWidth, specHeight);
}
} }
} }
} }
...@@ -759,7 +557,8 @@ public class InfoBarLayout extends ViewGroup implements View.OnClickListener { ...@@ -759,7 +557,8 @@ public class InfoBarLayout extends ViewGroup implements View.OnClickListener {
mInfoBarView.onButtonClicked(true); mInfoBarView.onButtonClicked(true);
} else if (view.getId() == R.id.button_secondary) { } else if (view.getId() == R.id.button_secondary) {
mInfoBarView.onButtonClicked(false); mInfoBarView.onButtonClicked(false);
} else if (view.getId() == R.id.button_tertiary) {
mInfoBarView.onLinkClicked();
} }
} }
} }
...@@ -4,52 +4,24 @@ ...@@ -4,52 +4,24 @@
package org.chromium.chrome.browser.infobar; package org.chromium.chrome.browser.infobar;
import android.content.Context;
/** /**
* Functions needed to display an InfoBar UI. * Functions needed to display an InfoBar UI.
*/ */
public interface InfoBarView { public interface InfoBarView {
/** /**
* Prepare the InfoBar for display and adding InfoBar-specific controls to the layout. * Prepares the InfoBar for display and adds InfoBar-specific controls to the layout.
* @param layout Layout containing all of the controls. * @param layout Layout containing all of the controls.
*/ */
public void createContent(InfoBarLayout layout); public void createContent(InfoBarLayout layout);
/** /**
* Returns the message indicating what the InfoBar is informing or asking the user about. * Takes some action related to the link being clicked.
* @param context Context to pull the string from.
* @return The string to display.
*/
public CharSequence getMessageText(Context context);
/**
* Returns text to display on the primary button indicating that some action will be taken.
* Setting this to null prevents the button from being created.
* @param context Context to pull the string from.
* @return The string to display.
*/
public String getPrimaryButtonText(Context context);
/**
* Returns text to display on the secondary button, typically indicating that some action will
* not be taken.
*
* Example text includes "Cancel" or "Nope". Setting this to null prevents the button from
* being created. It is illegal to have a secondary button without a primary button.
*
* @param context Context to pull the string from.
* @return The string to display.
*/
public String getSecondaryButtonText(Context context);
/**
* Take some action related to the link being clicked.
*/ */
public void onLinkClicked(); public void onLinkClicked();
/** /**
* Take some action related to the close button being clicked. * Takes some action related to the close button being clicked.
*/ */
public void onCloseButtonClicked(); public void onCloseButtonClicked();
......
...@@ -4,71 +4,33 @@ ...@@ -4,71 +4,33 @@
package org.chromium.chrome.browser.infobar; package org.chromium.chrome.browser.infobar;
import android.content.Context;
import org.chromium.chrome.R;
/** /**
* A simple infobar that contains a message and a close icon on the right side. * A simple infobar that contains a message and a close icon on the right side.
* This is used only in the context of Java code and is not associated with any native * This is used only in the context of Java code and is not associated with any native
* InfoBarDelegate. * InfoBarDelegate.
*
* TODO(newt): merge this into InfoBar.java
*/ */
public class MessageInfoBar extends InfoBar { public class MessageInfoBar extends InfoBar {
private final CharSequence mTitle;
/**
* Creates and returns an infobar with a white background and a close button on the right.
* @param title the text displayed in the infobar
* @return the infobar.
*/
public static MessageInfoBar createInfoBar(CharSequence title) {
return new MessageInfoBar(null, 0, title);
}
/**
* Creates and returns an infobar with a white background and a close button on the right.
* @param iconResourceId the icon shown on the right
* @param title the text displayed in the infobar
* @return the infobar.
*/
public static MessageInfoBar createInfoBar(int iconResourceId, CharSequence title) {
return new MessageInfoBar(null, iconResourceId, title);
}
/** /**
* Creates a warning infobar, with a yellow background and a warning icon on the right. * Creates an infobar with a message and a close button.
* @param title the text displayed in the infobar * @param title the text displayed in the infobar
* @return the infobar.
*/ */
public static MessageInfoBar createWarningInfoBar(CharSequence title) { public MessageInfoBar(CharSequence title) {
return createWarningInfoBar(null, title); this(null, 0, title);
} }
/** /**
* Creates a warning infobar, with a yellow background and a warning icon on the right. * Creates an infobar with an icon, a message and a close button.
* @param listener an infobar dismissed listener * @param listener A listener to be notified when the infobar is dismissed, or null.
* @param title the text displayed in the infobar * @param iconResourceId The icon to display in the infobar, or 0 if no icon should be shown.
* @return the infobar. * @param title The text to display in the infobar.
*/ */
public static MessageInfoBar createWarningInfoBar(InfoBarListeners.Dismiss listener, public MessageInfoBar(InfoBarListeners.Dismiss listener, int iconResourceId,
CharSequence title) {
return new MessageInfoBar(listener, R.drawable.infobar_warning, title);
}
protected MessageInfoBar(InfoBarListeners.Dismiss listener, int iconResourceId,
CharSequence title, int backgroundType) {
this(listener, iconResourceId, title);
}
protected MessageInfoBar(InfoBarListeners.Dismiss listener, int iconResourceId,
CharSequence title) { CharSequence title) {
super(listener, iconResourceId); super(listener, iconResourceId, title);
mTitle = title;
}
@Override
public CharSequence getMessageText(Context context) {
return mTitle;
} }
@Override @Override
......
...@@ -32,7 +32,7 @@ public class SavePasswordInfoBar extends ConfirmInfoBar { ...@@ -32,7 +32,7 @@ public class SavePasswordInfoBar extends ConfirmInfoBar {
mUseAdditionalAuthenticationCheckbox = new CheckBox(getContext()); mUseAdditionalAuthenticationCheckbox = new CheckBox(getContext());
mUseAdditionalAuthenticationCheckbox.setText( mUseAdditionalAuthenticationCheckbox.setText(
PasswordAuthenticationManager.getPasswordProtectionString()); PasswordAuthenticationManager.getPasswordProtectionString());
layout.addGroup(mUseAdditionalAuthenticationCheckbox); layout.setCustomContent(mUseAdditionalAuthenticationCheckbox);
} }
super.createContent(layout); super.createContent(layout);
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
package org.chromium.chrome.browser.infobar; package org.chromium.chrome.browser.infobar;
import android.content.Context; import android.content.Context;
import android.widget.TextView;
import org.chromium.chrome.R; import org.chromium.chrome.R;
...@@ -23,16 +22,15 @@ public class TranslateAlwaysPanel implements TranslateSubPanel { ...@@ -23,16 +22,15 @@ public class TranslateAlwaysPanel implements TranslateSubPanel {
@Override @Override
public void createContent(Context context, InfoBarLayout layout) { public void createContent(Context context, InfoBarLayout layout) {
TextView panelMessage = (TextView) layout.findViewById(R.id.infobar_message); layout.setMessage(context.getString(
panelMessage.setText(context.getString(
R.string.translate_infobar_translation_done, mOptions.targetLanguage())); R.string.translate_infobar_translation_done, mOptions.targetLanguage()));
if (!mOptions.triggeredFromMenu()) { if (!mOptions.triggeredFromMenu()) {
TranslateCheckBox checkBox = new TranslateCheckBox(mOptions, mListener); TranslateCheckBox checkBox = new TranslateCheckBox(context, mOptions, mListener);
checkBox.createContent(context, layout); layout.setCustomContent(checkBox);
} }
layout.addButtons(context.getString(R.string.translate_button_done), layout.setButtons(context.getString(R.string.translate_button_done),
context.getString(R.string.translate_show_original)); context.getString(R.string.translate_show_original));
} }
......
...@@ -6,38 +6,35 @@ package org.chromium.chrome.browser.infobar; ...@@ -6,38 +6,35 @@ package org.chromium.chrome.browser.infobar;
import android.content.Context; import android.content.Context;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import org.chromium.chrome.R; import org.chromium.chrome.R;
/** /**
* A check box used to determine if a page should always be translated. * A check box used to determine if a page should always be translated.
*/ */
public class TranslateCheckBox { public class TranslateCheckBox extends CheckBox implements OnCheckedChangeListener {
private final SubPanelListener mListener; private final SubPanelListener mListener;
private final TranslateOptions mOptions; private final TranslateOptions mOptions;
public TranslateCheckBox(TranslateOptions options, SubPanelListener listener) { public TranslateCheckBox(Context context, TranslateOptions options, SubPanelListener listener) {
super(context);
mOptions = options; mOptions = options;
mListener = listener; mListener = listener;
setId(R.id.infobar_extra_check);
setText(context.getString(R.string.translate_always_text, mOptions.sourceLanguage()));
setChecked(mOptions.alwaysTranslateLanguageState());
setOnCheckedChangeListener(this);
} }
public void createContent(Context context, InfoBarLayout layout) { @Override
CheckBox checkBox = new CheckBox(context); public void onCheckedChanged(CompoundButton view, boolean isChecked) {
checkBox.setId(R.id.infobar_extra_check); mOptions.toggleAlwaysTranslateLanguageState(isChecked);
checkBox.setText(context.getString(R.string.translate_always_text, if (isChecked) {
mOptions.sourceLanguage())); mListener.onPanelClosed(InfoBar.ACTION_TYPE_NONE);
checkBox.setChecked(mOptions.alwaysTranslateLanguageState()); } else {
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { mListener.onOptionsChanged();
@Override }
public void onCheckedChanged(CompoundButton view, boolean isChecked) {
mOptions.toggleAlwaysTranslateLanguageState(isChecked);
if (isChecked) {
mListener.onPanelClosed(InfoBar.ACTION_TYPE_NONE);
} else {
mListener.onOptionsChanged();
}
}
});
layout.addGroup(checkBox);
} }
} }
...@@ -19,7 +19,7 @@ import org.chromium.ui.base.DeviceFormFactor; ...@@ -19,7 +19,7 @@ import org.chromium.ui.base.DeviceFormFactor;
/** /**
* Java version of the translate infobar * Java version of the translate infobar
*/ */
public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListener { public class TranslateInfoBar extends InfoBar implements SubPanelListener {
// Needs to be kept in sync with the Type enum in translate_infobar_delegate.h. // Needs to be kept in sync with the Type enum in translate_infobar_delegate.h.
public static final int BEFORE_TRANSLATE_INFOBAR = 0; public static final int BEFORE_TRANSLATE_INFOBAR = 0;
public static final int TRANSLATING_INFOBAR = 1; public static final int TRANSLATING_INFOBAR = 1;
...@@ -45,8 +45,7 @@ public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListen ...@@ -45,8 +45,7 @@ public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListen
int infoBarType, int sourceLanguageIndex, int targetLanguageIndex, int infoBarType, int sourceLanguageIndex, int targetLanguageIndex,
boolean autoTranslatePair, boolean shouldShowNeverBar, boolean autoTranslatePair, boolean shouldShowNeverBar,
boolean triggeredFromMenu, String[] languages) { boolean triggeredFromMenu, String[] languages) {
super(null, R.drawable.infobar_translate); super(null, R.drawable.infobar_translate, null);
mTranslateDelegate = delegate; mTranslateDelegate = delegate;
mOptions = new TranslateOptions(sourceLanguageIndex, targetLanguageIndex, languages, mOptions = new TranslateOptions(sourceLanguageIndex, targetLanguageIndex, languages,
autoTranslatePair, triggeredFromMenu); autoTranslatePair, triggeredFromMenu);
...@@ -111,8 +110,7 @@ public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListen ...@@ -111,8 +110,7 @@ public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListen
return action; return action;
} }
@Override private CharSequence getMessageText(Context context) {
public CharSequence getMessageText(Context context) {
switch (getInfoBarType()) { switch (getInfoBarType()) {
case BEFORE_TRANSLATE_INFOBAR: case BEFORE_TRANSLATE_INFOBAR:
String template = context.getString(R.string.translate_infobar_text); String template = context.getString(R.string.translate_infobar_text);
...@@ -137,8 +135,7 @@ public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListen ...@@ -137,8 +135,7 @@ public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListen
} }
} }
@Override private String getPrimaryButtonText(Context context) {
public String getPrimaryButtonText(Context context) {
switch (getInfoBarType()) { switch (getInfoBarType()) {
case BEFORE_TRANSLATE_INFOBAR: case BEFORE_TRANSLATE_INFOBAR:
return context.getString(R.string.translate_button); return context.getString(R.string.translate_button);
...@@ -154,8 +151,7 @@ public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListen ...@@ -154,8 +151,7 @@ public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListen
} }
} }
@Override private String getSecondaryButtonText(Context context) {
public String getSecondaryButtonText(Context context) {
switch (getInfoBarType()) { switch (getInfoBarType()) {
case BEFORE_TRANSLATE_INFOBAR: case BEFORE_TRANSLATE_INFOBAR:
return context.getString(R.string.translate_nope); return context.getString(R.string.translate_nope);
...@@ -181,15 +177,17 @@ public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListen ...@@ -181,15 +177,17 @@ public class TranslateInfoBar extends TwoButtonInfoBar implements SubPanelListen
return; return;
} }
Context context = layout.getContext();
layout.setMessage(getMessageText(context));
layout.setButtons(getPrimaryButtonText(context), getSecondaryButtonText(context));
if (getInfoBarType() == AFTER_TRANSLATE_INFOBAR && if (getInfoBarType() == AFTER_TRANSLATE_INFOBAR &&
!needsAlwaysPanel() && !needsAlwaysPanel() &&
!mOptions.triggeredFromMenu()) { !mOptions.triggeredFromMenu()) {
// Long always translate version // Long always translate version
TranslateCheckBox checkBox = new TranslateCheckBox(mOptions, this); TranslateCheckBox checkBox = new TranslateCheckBox(context, mOptions, this);
checkBox.createContent(getContext(), layout); layout.setCustomContent(checkBox);
} }
super.createContent(layout);
} }
// SubPanelListener implementation // SubPanelListener implementation
......
...@@ -70,15 +70,14 @@ public class TranslateLanguagePanel ...@@ -70,15 +70,14 @@ public class TranslateLanguagePanel
mTargetSpinner = null; mTargetSpinner = null;
String changeLanguage = context.getString(R.string.translate_infobar_change_languages); String changeLanguage = context.getString(R.string.translate_infobar_change_languages);
TextView panelMessage = (TextView) layout.findViewById(R.id.infobar_message); layout.setMessage(changeLanguage);
panelMessage.setText(changeLanguage);
// Set up the spinners. // Set up the spinners.
createSpinners(context); createSpinners(context);
layout.addGroup(mSourceSpinner, mTargetSpinner); layout.setCustomContent(mSourceSpinner, mTargetSpinner);
// Set up the buttons. // Set up the buttons.
layout.addButtons(context.getString(R.string.translate_button_done), layout.setButtons(context.getString(R.string.translate_button_done),
context.getString(R.string.cancel)); context.getString(R.string.cancel));
} }
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
package org.chromium.chrome.browser.infobar; package org.chromium.chrome.browser.infobar;
import android.content.Context; import android.content.Context;
import android.widget.TextView;
import org.chromium.chrome.R; import org.chromium.chrome.R;
...@@ -25,11 +24,9 @@ public class TranslateNeverPanel implements TranslateSubPanel { ...@@ -25,11 +24,9 @@ public class TranslateNeverPanel implements TranslateSubPanel {
public void createContent(Context context, InfoBarLayout layout) { public void createContent(Context context, InfoBarLayout layout) {
String changeLanguage = context.getString( String changeLanguage = context.getString(
R.string.translate_never_translate_message_text, mOptions.sourceLanguage()); R.string.translate_never_translate_message_text, mOptions.sourceLanguage());
layout.setMessage(changeLanguage);
TextView panelMessage = (TextView) layout.findViewById(R.id.infobar_message); layout.setButtons(
panelMessage.setText(changeLanguage);
layout.addButtons(
context.getString(R.string.translate_never_translate_site), context.getString(R.string.translate_never_translate_site),
context.getString(R.string.translate_never_translate_language, context.getString(R.string.translate_never_translate_language,
mOptions.sourceLanguage())); mOptions.sourceLanguage()));
......
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.infobar;
import android.content.Context;
import android.widget.Button;
import org.chromium.chrome.R;
/**
* An infobar that presents the user with up to 2 buttons.
*/
public abstract class TwoButtonInfoBar extends InfoBar {
public TwoButtonInfoBar(InfoBarListeners.Dismiss dismissListener, int backgroundType,
int iconDrawableId) {
super(dismissListener, iconDrawableId);
}
public TwoButtonInfoBar(InfoBarListeners.Dismiss dismissListener, int iconDrawableId) {
super(dismissListener, iconDrawableId);
}
/**
* Creates controls for the current InfoBar.
* @param layout InfoBarLayout to find controls in.
*/
@Override
public void createContent(InfoBarLayout layout) {
Context context = layout.getContext();
layout.addButtons(getPrimaryButtonText(context), getSecondaryButtonText(context));
}
@Override
public void setControlsEnabled(boolean state) {
super.setControlsEnabled(state);
// Handle the buttons.
ContentWrapperView wrapper = getContentWrapper(false);
if (wrapper != null) {
Button primary = (Button) wrapper.findViewById(R.id.button_primary);
Button secondary = (Button) wrapper.findViewById(R.id.button_secondary);
if (primary != null) primary.setEnabled(state);
if (secondary != null) secondary.setEnabled(state);
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment