1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 package org.mozilla.gecko;
6 
7 import android.content.res.TypedArray;
8 import android.graphics.Canvas;
9 import android.graphics.Paint;
10 import org.mozilla.gecko.animation.AnimationUtils;
11 import org.mozilla.gecko.menu.GeckoMenu;
12 import org.mozilla.gecko.widget.GeckoPopupMenu;
13 
14 import android.content.Context;
15 import android.util.AttributeSet;
16 import android.view.LayoutInflater;
17 import android.view.Menu;
18 import android.view.View;
19 import android.view.ViewGroup;
20 import android.view.animation.Animation;
21 import android.view.animation.ScaleAnimation;
22 import android.view.animation.TranslateAnimation;
23 import android.widget.Button;
24 import android.widget.ImageButton;
25 import android.widget.LinearLayout;
26 
27 class ActionModeCompatView extends LinearLayout implements GeckoMenu.ActionItemBarPresenter {
28     private final String LOGTAG = "GeckoActionModeCompatPresenter";
29 
30     private static final int SPEC = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
31 
32     private Button mTitleView;
33     private ImageButton mMenuButton;
34     private ViewGroup mActionButtonBar;
35     private GeckoPopupMenu mPopupMenu;
36 
37     // Maximum number of items to show as actions
38     private static final int MAX_ACTION_ITEMS = 4;
39 
40     private int mActionButtonsWidth;
41 
42     private Paint mBottomDividerPaint;
43     private int mBottomDividerOffset;
44 
ActionModeCompatView(Context context, AttributeSet attrs)45     public ActionModeCompatView(Context context, AttributeSet attrs) {
46         super(context, attrs);
47         init(context, attrs, 0);
48     }
49 
ActionModeCompatView(Context context, AttributeSet attrs, int style)50     public ActionModeCompatView(Context context, AttributeSet attrs, int style) {
51         super(context, attrs, style);
52         init(context, attrs, style);
53     }
54 
init(final Context context, final AttributeSet attrs, final int defStyle)55     public void init(final Context context, final AttributeSet attrs, final int defStyle) {
56         LayoutInflater.from(context).inflate(R.layout.actionbar, this);
57 
58         mTitleView = (Button) findViewById(R.id.actionmode_title);
59         mMenuButton = (ImageButton) findViewById(R.id.actionbar_menu);
60         mActionButtonBar = (ViewGroup) findViewById(R.id.actionbar_buttons);
61 
62         mPopupMenu = new GeckoPopupMenu(getContext(), mMenuButton);
63         mPopupMenu.getMenu().setActionItemBarPresenter(this);
64 
65         mMenuButton.setOnClickListener(new View.OnClickListener() {
66             @Override
67             public void onClick(View v) {
68                 openMenu();
69             }
70         });
71 
72         // The built-in action bar uses colorAccent for the divider so we duplicate that here.
73         final TypedArray arr = context.obtainStyledAttributes(attrs, new int[] { R.attr.colorAccent }, defStyle, 0);
74         final int bottomDividerColor = arr.getColor(0, 0);
75         arr.recycle();
76 
77         mBottomDividerPaint = new Paint();
78         mBottomDividerPaint.setColor(bottomDividerColor);
79         mBottomDividerOffset = getResources().getDimensionPixelSize(R.dimen.action_bar_divider_height);
80     }
81 
initForMode(final ActionModeCompat mode)82     public void initForMode(final ActionModeCompat mode) {
83         mTitleView.setOnClickListener(mode);
84         mPopupMenu.setOnMenuItemClickListener(mode);
85         mPopupMenu.setOnMenuItemLongClickListener(mode);
86     }
87 
getTitle()88     public CharSequence getTitle() {
89         return mTitleView.getText();
90     }
91 
setTitle(CharSequence title)92     public void setTitle(CharSequence title) {
93         mTitleView.setText(title);
94     }
95 
setTitle(int resId)96     public void setTitle(int resId) {
97         mTitleView.setText(resId);
98     }
99 
getMenu()100     public GeckoMenu getMenu() {
101         return mPopupMenu.getMenu();
102     }
103 
104     @Override
invalidate()105     public void invalidate() {
106         // onFinishInflate may not have been called yet on some versions of Android
107         if (mPopupMenu != null && mMenuButton != null) {
108             mMenuButton.setVisibility(mPopupMenu.getMenu().hasVisibleItems() ? View.VISIBLE : View.GONE);
109         }
110         super.invalidate();
111     }
112 
113     /* GeckoMenu.ActionItemBarPresenter */
114     @Override
addActionItem(View actionItem)115     public boolean addActionItem(View actionItem) {
116         final int count = mActionButtonBar.getChildCount();
117         if (count >= MAX_ACTION_ITEMS) {
118             return false;
119         }
120 
121         int maxWidth = mActionButtonBar.getMeasuredWidth();
122         if (maxWidth == 0) {
123             mActionButtonBar.measure(SPEC, SPEC);
124             maxWidth = mActionButtonBar.getMeasuredWidth();
125         }
126 
127         // If the menu button is already visible, no need to account for it
128         if (mMenuButton.getVisibility() == View.GONE) {
129             // Since we don't know how many items will be added, we always reserve space for the overflow menu
130             mMenuButton.measure(SPEC, SPEC);
131             maxWidth -= mMenuButton.getMeasuredWidth();
132         }
133 
134         if (mActionButtonsWidth <= 0) {
135             mActionButtonsWidth = 0;
136 
137             // Loop over child views, measure them, and add their width to the taken width
138             for (int i = 0; i < count; i++) {
139                 View v = mActionButtonBar.getChildAt(i);
140                 v.measure(SPEC, SPEC);
141                 mActionButtonsWidth += v.getMeasuredWidth();
142             }
143         }
144 
145         actionItem.measure(SPEC, SPEC);
146         int w = actionItem.getMeasuredWidth();
147         if (mActionButtonsWidth + w < maxWidth) {
148             // We cache the new width of our children.
149             mActionButtonsWidth += w;
150             mActionButtonBar.addView(actionItem);
151             return true;
152         }
153 
154         return false;
155     }
156 
157     /* GeckoMenu.ActionItemBarPresenter */
158     @Override
removeActionItem(View actionItem)159     public void removeActionItem(View actionItem) {
160         actionItem.measure(SPEC, SPEC);
161         mActionButtonsWidth -= actionItem.getMeasuredWidth();
162         mActionButtonBar.removeView(actionItem);
163     }
164 
openMenu()165     public void openMenu() {
166         mPopupMenu.openMenu();
167     }
168 
closeMenu()169     public void closeMenu() {
170         mPopupMenu.dismiss();
171     }
172 
animateIn()173     public void animateIn() {
174         long duration = AnimationUtils.getShortDuration(getContext());
175         TranslateAnimation t = new TranslateAnimation(Animation.RELATIVE_TO_SELF, -0.5f, Animation.RELATIVE_TO_SELF, 0f,
176                                                       Animation.RELATIVE_TO_SELF,  0f,   Animation.RELATIVE_TO_SELF, 0f);
177         t.setDuration(duration);
178 
179         ScaleAnimation s = new ScaleAnimation(1f, 1f, 0f, 1f,
180                                               Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
181         s.setDuration((long) (duration * 1.5f));
182 
183         mTitleView.startAnimation(t);
184         mActionButtonBar.startAnimation(s);
185 
186         if ((mMenuButton.getVisibility() == View.VISIBLE) &&
187             (mPopupMenu.getMenu().size() > 0)) {
188             mMenuButton.startAnimation(s);
189         }
190     }
191 
192     @Override
onDraw(Canvas canvas)193     protected void onDraw(Canvas canvas) {
194         super.onDraw(canvas);
195 
196         // Draw the divider at the bottom of the screen. We could do this with a layer-list
197         // but then we'd have overdraw (http://stackoverflow.com/a/13509472).
198         final int bottom = getHeight();
199         final int top = bottom - mBottomDividerOffset;
200         canvas.drawRect(0, top, getWidth(), bottom, mBottomDividerPaint);
201     }
202 }
203