IconMenuView.java revision 189ee18d6c6483ad63cc864267328259e2e00b95
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.view.menu;
18
19import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
20
21import android.content.Context;
22import android.content.res.Resources;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.util.AttributeSet;
30import android.view.KeyEvent;
31import android.view.View;
32import android.view.ViewConfiguration;
33import android.view.ViewGroup;
34import android.view.LayoutInflater;
35
36import java.util.ArrayList;
37
38/**
39 * The icon menu view is an icon-based menu usually with a subset of all the menu items.
40 * It is opened as the default menu, and shows either the first five or all six of the menu items
41 * with text and icon.  In the situation of there being more than six items, the first five items
42 * will be accompanied with a 'More' button that opens an {@link ExpandedMenuView} which lists
43 * all the menu items.
44 *
45 * @attr ref android.R.styleable#IconMenuView_rowHeight
46 * @attr ref android.R.styleable#IconMenuView_maxRows
47 * @attr ref android.R.styleable#IconMenuView_maxItemsPerRow
48 *
49 * @hide
50 */
51public final class IconMenuView extends ViewGroup implements ItemInvoker, MenuView, Runnable {
52    private static final int ITEM_CAPTION_CYCLE_DELAY = 1000;
53
54    private MenuBuilder mMenu;
55
56    /** Height of each row */
57    private int mRowHeight;
58    /** Maximum number of rows to be shown */
59    private int mMaxRows;
60    /** Maximum number of items to show in the icon menu. */
61    private int mMaxItems;
62    /** Maximum number of items per row */
63    private int mMaxItemsPerRow;
64    /** Actual number of items (the 'More' view does not count as an item) shown */
65    private int mNumActualItemsShown;
66
67    /** Divider that is drawn between all rows */
68    private Drawable mHorizontalDivider;
69    /** Height of the horizontal divider */
70    private int mHorizontalDividerHeight;
71    /** Set of horizontal divider positions where the horizontal divider will be drawn */
72    private ArrayList<Rect> mHorizontalDividerRects;
73
74    /** Divider that is drawn between all columns */
75    private Drawable mVerticalDivider;
76    /** Width of the vertical divider */
77    private int mVerticalDividerWidth;
78    /** Set of vertical divider positions where the vertical divider will be drawn */
79    private ArrayList<Rect> mVerticalDividerRects;
80
81    /** Icon for the 'More' button */
82    private Drawable mMoreIcon;
83
84    /** Item view for the 'More' button */
85    private IconMenuItemView mMoreItemView;
86
87    /** Background of each item (should contain the selected and focused states) */
88    private Drawable mItemBackground;
89
90    /** Default animations for this menu */
91    private int mAnimations;
92
93    /**
94     * Whether this IconMenuView has stale children and needs to update them.
95     * Set true by {@link #markStaleChildren()} and reset to false by
96     * {@link #onMeasure(int, int)}
97     */
98    private boolean mHasStaleChildren;
99
100    /**
101     * Longpress on MENU (while this is shown) switches to shortcut caption
102     * mode. When the user releases the longpress, we do not want to pass the
103     * key-up event up since that will dismiss the menu.
104     */
105    private boolean mMenuBeingLongpressed = false;
106
107    /**
108     * While {@link #mMenuBeingLongpressed}, we toggle the children's caption
109     * mode between each's title and its shortcut. This is the last caption mode
110     * we broadcasted to children.
111     */
112    private boolean mLastChildrenCaptionMode;
113
114    /**
115     * The layout to use for menu items. Each index is the row number (0 is the
116     * top-most). Each value contains the number of items in that row.
117     * <p>
118     * The length of this array should not be used to get the number of rows in
119     * the current layout, instead use {@link #mLayoutNumRows}.
120     */
121    private int[] mLayout;
122
123    /**
124     * The number of rows in the current layout.
125     */
126    private int mLayoutNumRows;
127
128    /**
129     * Instantiates the IconMenuView that is linked with the provided MenuBuilder.
130     */
131    public IconMenuView(Context context, AttributeSet attrs) {
132        super(context, attrs);
133
134        TypedArray a =
135            context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.IconMenuView, 0, 0);
136        mRowHeight = a.getDimensionPixelSize(com.android.internal.R.styleable.IconMenuView_rowHeight, 64);
137        mMaxRows = a.getInt(com.android.internal.R.styleable.IconMenuView_maxRows, 2);
138        mMaxItems = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItems, 6);
139        mMaxItemsPerRow = a.getInt(com.android.internal.R.styleable.IconMenuView_maxItemsPerRow, 3);
140        mMoreIcon = a.getDrawable(com.android.internal.R.styleable.IconMenuView_moreIcon);
141        a.recycle();
142
143        a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MenuView, 0, 0);
144        mItemBackground = a.getDrawable(com.android.internal.R.styleable.MenuView_itemBackground);
145        mHorizontalDivider = a.getDrawable(com.android.internal.R.styleable.MenuView_horizontalDivider);
146        mHorizontalDividerRects = new ArrayList<Rect>();
147        mVerticalDivider =  a.getDrawable(com.android.internal.R.styleable.MenuView_verticalDivider);
148        mVerticalDividerRects = new ArrayList<Rect>();
149        mAnimations = a.getResourceId(com.android.internal.R.styleable.MenuView_windowAnimationStyle, 0);
150        a.recycle();
151
152        if (mHorizontalDivider != null) {
153            mHorizontalDividerHeight = mHorizontalDivider.getIntrinsicHeight();
154            // Make sure to have some height for the divider
155            if (mHorizontalDividerHeight == -1) mHorizontalDividerHeight = 1;
156        }
157
158        if (mVerticalDivider != null) {
159            mVerticalDividerWidth = mVerticalDivider.getIntrinsicWidth();
160            // Make sure to have some width for the divider
161            if (mVerticalDividerWidth == -1) mVerticalDividerWidth = 1;
162        }
163
164        mLayout = new int[mMaxRows];
165
166        // This view will be drawing the dividers
167        setWillNotDraw(false);
168
169        // This is so we'll receive the MENU key in touch mode
170        setFocusableInTouchMode(true);
171        // This is so our children can still be arrow-key focused
172        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
173    }
174
175    /**
176     * Figures out the layout for the menu items.
177     *
178     * @param width The available width for the icon menu.
179     */
180    private void layoutItems(int width) {
181        int numItems = getChildCount();
182        if (numItems == 0) {
183            mLayoutNumRows = 0;
184            return;
185        }
186
187        // Start with the least possible number of rows
188        int curNumRows =
189                Math.min((int) Math.ceil(numItems / (float) mMaxItemsPerRow), mMaxRows);
190
191        /*
192         * Increase the number of rows until we find a configuration that fits
193         * all of the items' titles. Worst case, we use mMaxRows.
194         */
195        for (; curNumRows <= mMaxRows; curNumRows++) {
196            layoutItemsUsingGravity(curNumRows, numItems);
197
198            if (curNumRows >= numItems) {
199                // Can't have more rows than items
200                break;
201            }
202
203            if (doItemsFit()) {
204                // All the items fit, so this is a good configuration
205                break;
206            }
207        }
208    }
209
210    /**
211     * Figures out the layout for the menu items by equally distributing, and
212     * adding any excess items equally to lower rows.
213     *
214     * @param numRows The total number of rows for the menu view
215     * @param numItems The total number of items (across all rows) contained in
216     *            the menu view
217     * @return int[] Where the value of index i contains the number of items for row i
218     */
219    private void layoutItemsUsingGravity(int numRows, int numItems) {
220        int numBaseItemsPerRow = numItems / numRows;
221        int numLeftoverItems = numItems % numRows;
222        /**
223         * The bottom rows will each get a leftover item. Rows (indexed at 0)
224         * that are >= this get a leftover item. Note: if there are 0 leftover
225         * items, no rows will get them since this value will be greater than
226         * the last row.
227         */
228        int rowsThatGetALeftoverItem = numRows - numLeftoverItems;
229
230        int[] layout = mLayout;
231        for (int i = 0; i < numRows; i++) {
232            layout[i] = numBaseItemsPerRow;
233
234            // Fill the bottom rows with a leftover item each
235            if (i >= rowsThatGetALeftoverItem) {
236                layout[i]++;
237            }
238        }
239
240        mLayoutNumRows = numRows;
241    }
242
243    /**
244     * Checks whether each item's title is fully visible using the current
245     * layout.
246     *
247     * @return True if the items fit (each item's text is fully visible), false
248     *         otherwise.
249     */
250    private boolean doItemsFit() {
251        int itemPos = 0;
252
253        int[] layout = mLayout;
254        int numRows = mLayoutNumRows;
255        for (int row = 0; row < numRows; row++) {
256            int numItemsOnRow = layout[row];
257
258            /*
259             * If there is only one item on this row, increasing the
260             * number of rows won't help.
261             */
262            if (numItemsOnRow == 1) {
263                itemPos++;
264                continue;
265            }
266
267            for (int itemsOnRowCounter = numItemsOnRow; itemsOnRowCounter > 0;
268                    itemsOnRowCounter--) {
269                View child = getChildAt(itemPos++);
270                LayoutParams lp = (LayoutParams) child.getLayoutParams();
271                if (lp.maxNumItemsOnRow < numItemsOnRow) {
272                    return false;
273                }
274            }
275        }
276
277        return true;
278    }
279
280    /**
281     * Adds an IconMenuItemView to this icon menu view.
282     * @param itemView The item's view to add
283     */
284    private void addItemView(IconMenuItemView itemView) {
285        // Set ourselves on the item view
286        itemView.setIconMenuView(this);
287
288        // Apply the background to the item view
289        itemView.setBackgroundDrawable(
290                mItemBackground.getConstantState().newDrawable(
291                        getContext().getResources()));
292
293        // This class is the invoker for all its item views
294        itemView.setItemInvoker(this);
295
296        addView(itemView, itemView.getTextAppropriateLayoutParams());
297    }
298
299    /**
300     * Creates the item view for the 'More' button which is used to switch to
301     * the expanded menu view. This button is a special case since it does not
302     * have a MenuItemData backing it.
303     * @return The IconMenuItemView for the 'More' button
304     */
305    private IconMenuItemView createMoreItemView() {
306        LayoutInflater inflater = mMenu.getMenuType(MenuBuilder.TYPE_ICON).getInflater();
307
308        final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate(
309                com.android.internal.R.layout.icon_menu_item_layout, null);
310
311        Resources r = getContext().getResources();
312        itemView.initialize(r.getText(com.android.internal.R.string.more_item_label), mMoreIcon);
313
314        // Set up a click listener on the view since there will be no invocation sequence
315        // due to the lack of a MenuItemData this view
316        itemView.setOnClickListener(new OnClickListener() {
317            public void onClick(View v) {
318                // Switches the menu to expanded mode
319                MenuBuilder.Callback cb = mMenu.getCallback();
320                if (cb != null) {
321                    // Call callback
322                    cb.onMenuModeChange(mMenu);
323                }
324            }
325        });
326
327        return itemView;
328    }
329
330
331    public void initialize(MenuBuilder menu, int menuType) {
332        mMenu = menu;
333        updateChildren(true);
334    }
335
336    public void updateChildren(boolean cleared) {
337        // This method does a clear refresh of children
338        removeAllViews();
339
340        // IconMenuView never wants content sorted for an overflow action button, since
341        // it is never used in the presence of an overflow button.
342        final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems(false);
343        final int numItems = itemsToShow.size();
344        final int numItemsThatCanFit = mMaxItems;
345        // Minimum of the num that can fit and the num that we have
346        final int minFitMinus1AndNumItems = Math.min(numItemsThatCanFit - 1, numItems);
347
348        MenuItemImpl itemData;
349        // Traverse through all but the last item that can fit since that last item can either
350        // be a 'More' button or a sixth item
351        for (int i = 0; i < minFitMinus1AndNumItems; i++) {
352            itemData = itemsToShow.get(i);
353            addItemView((IconMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ICON, this));
354        }
355
356        if (numItems > numItemsThatCanFit) {
357            // If there are more items than we can fit, show the 'More' button to
358            // switch to expanded mode
359            if (mMoreItemView == null) {
360                mMoreItemView = createMoreItemView();
361            }
362
363            addItemView(mMoreItemView);
364
365            // The last view is the more button, so the actual number of items is one less than
366            // the number that can fit
367            mNumActualItemsShown = numItemsThatCanFit - 1;
368        } else if (numItems == numItemsThatCanFit) {
369            // There are exactly the number we can show, so show the last item
370            final MenuItemImpl lastItemData = itemsToShow.get(numItemsThatCanFit - 1);
371            addItemView((IconMenuItemView) lastItemData.getItemView(MenuBuilder.TYPE_ICON, this));
372
373            // The items shown fit exactly
374            mNumActualItemsShown = numItemsThatCanFit;
375        }
376    }
377
378    /**
379     * The positioning algorithm that gets called from onMeasure.  It
380     * just computes positions for each child, and then stores them in the child's layout params.
381     * @param menuWidth The width of this menu to assume for positioning
382     * @param menuHeight The height of this menu to assume for positioning
383     */
384    private void positionChildren(int menuWidth, int menuHeight) {
385        // Clear the containers for the positions where the dividers should be drawn
386        if (mHorizontalDivider != null) mHorizontalDividerRects.clear();
387        if (mVerticalDivider != null) mVerticalDividerRects.clear();
388
389        // Get the minimum number of rows needed
390        final int numRows = mLayoutNumRows;
391        final int numRowsMinus1 = numRows - 1;
392        final int numItemsForRow[] = mLayout;
393
394        // The item position across all rows
395        int itemPos = 0;
396        View child;
397        IconMenuView.LayoutParams childLayoutParams = null;
398
399        // Use float for this to get precise positions (uniform item widths
400        // instead of last one taking any slack), and then convert to ints at last opportunity
401        float itemLeft;
402        float itemTop = 0;
403        // Since each row can have a different number of items, this will be computed per row
404        float itemWidth;
405        // Subtract the space needed for the horizontal dividers
406        final float itemHeight = (menuHeight - mHorizontalDividerHeight * (numRows - 1))
407                / (float)numRows;
408
409        for (int row = 0; row < numRows; row++) {
410            // Start at the left
411            itemLeft = 0;
412
413            // Subtract the space needed for the vertical dividers, and divide by the number of items
414            itemWidth = (menuWidth - mVerticalDividerWidth * (numItemsForRow[row] - 1))
415                    / (float)numItemsForRow[row];
416
417            for (int itemPosOnRow = 0; itemPosOnRow < numItemsForRow[row]; itemPosOnRow++) {
418                // Tell the child to be exactly this size
419                child = getChildAt(itemPos);
420                child.measure(MeasureSpec.makeMeasureSpec((int) itemWidth, MeasureSpec.EXACTLY),
421                        MeasureSpec.makeMeasureSpec((int) itemHeight, MeasureSpec.EXACTLY));
422
423                // Remember the child's position for layout
424                childLayoutParams = (IconMenuView.LayoutParams) child.getLayoutParams();
425                childLayoutParams.left = (int) itemLeft;
426                childLayoutParams.right = (int) (itemLeft + itemWidth);
427                childLayoutParams.top = (int) itemTop;
428                childLayoutParams.bottom = (int) (itemTop + itemHeight);
429
430                // Increment by item width
431                itemLeft += itemWidth;
432                itemPos++;
433
434                // Add a vertical divider to draw
435                if (mVerticalDivider != null) {
436                    mVerticalDividerRects.add(new Rect((int) itemLeft,
437                            (int) itemTop, (int) (itemLeft + mVerticalDividerWidth),
438                            (int) (itemTop + itemHeight)));
439                }
440
441                // Increment by divider width (even if we're not computing
442                // dividers, since we need to leave room for them when
443                // calculating item positions)
444                itemLeft += mVerticalDividerWidth;
445            }
446
447            // Last child on each row should extend to very right edge
448            if (childLayoutParams != null) {
449                childLayoutParams.right = menuWidth;
450            }
451
452            itemTop += itemHeight;
453
454            // Add a horizontal divider to draw
455            if ((mHorizontalDivider != null) && (row < numRowsMinus1)) {
456                mHorizontalDividerRects.add(new Rect(0, (int) itemTop, menuWidth,
457                        (int) (itemTop + mHorizontalDividerHeight)));
458
459                itemTop += mHorizontalDividerHeight;
460            }
461        }
462    }
463
464    @Override
465    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
466        if (mHasStaleChildren) {
467            mHasStaleChildren = false;
468
469            // If we have stale data, resync with the menu
470            updateChildren(false);
471        }
472
473        int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec);
474        calculateItemFittingMetadata(measuredWidth);
475        layoutItems(measuredWidth);
476
477        // Get the desired height of the icon menu view (last row of items does
478        // not have a divider below)
479        final int layoutNumRows = mLayoutNumRows;
480        final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) *
481                layoutNumRows - mHorizontalDividerHeight;
482
483        // Maximum possible width and desired height
484        setMeasuredDimension(measuredWidth,
485                resolveSize(desiredHeight, heightMeasureSpec));
486
487        // Position the children
488        if (layoutNumRows > 0) {
489            positionChildren(getMeasuredWidth(), getMeasuredHeight());
490        }
491    }
492
493
494    @Override
495    protected void onLayout(boolean changed, int l, int t, int r, int b) {
496        View child;
497        IconMenuView.LayoutParams childLayoutParams;
498
499        for (int i = getChildCount() - 1; i >= 0; i--) {
500            child = getChildAt(i);
501            childLayoutParams = (IconMenuView.LayoutParams)child
502                    .getLayoutParams();
503
504            // Layout children according to positions set during the measure
505            child.layout(childLayoutParams.left, childLayoutParams.top, childLayoutParams.right,
506                    childLayoutParams.bottom);
507        }
508    }
509
510    @Override
511    protected void onDraw(Canvas canvas) {
512        Drawable drawable = mHorizontalDivider;
513        if (drawable != null) {
514            // If we have a horizontal divider to draw, draw it at the remembered positions
515            final ArrayList<Rect> rects = mHorizontalDividerRects;
516            for (int i = rects.size() - 1; i >= 0; i--) {
517                drawable.setBounds(rects.get(i));
518                drawable.draw(canvas);
519            }
520        }
521
522        drawable = mVerticalDivider;
523        if (drawable != null) {
524            // If we have a vertical divider to draw, draw it at the remembered positions
525            final ArrayList<Rect> rects = mVerticalDividerRects;
526            for (int i = rects.size() - 1; i >= 0; i--) {
527                drawable.setBounds(rects.get(i));
528                drawable.draw(canvas);
529            }
530        }
531    }
532
533    public boolean invokeItem(MenuItemImpl item) {
534        return mMenu.performItemAction(item, 0);
535    }
536
537    @Override
538    public LayoutParams generateLayoutParams(AttributeSet attrs) {
539        return new IconMenuView.LayoutParams(getContext(), attrs);
540    }
541
542    @Override
543    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
544        // Override to allow type-checking of LayoutParams.
545        return p instanceof IconMenuView.LayoutParams;
546    }
547
548    /**
549     * Marks as having stale children.
550     */
551    void markStaleChildren() {
552        if (!mHasStaleChildren) {
553            mHasStaleChildren = true;
554            requestLayout();
555        }
556    }
557
558    /**
559     * @return The number of actual items shown (those that are backed by an
560     *         {@link MenuView.ItemView} implementation--eg: excludes More
561     *         item).
562     */
563    int getNumActualItemsShown() {
564        return mNumActualItemsShown;
565    }
566
567
568    public int getWindowAnimations() {
569        return mAnimations;
570    }
571
572    /**
573     * Returns the number of items per row.
574     * <p>
575     * This should only be used for testing.
576     *
577     * @return The length of the array is the number of rows. A value at a
578     *         position is the number of items in that row.
579     * @hide
580     */
581    public int[] getLayout() {
582        return mLayout;
583    }
584
585    /**
586     * Returns the number of rows in the layout.
587     * <p>
588     * This should only be used for testing.
589     *
590     * @return The length of the array is the number of rows. A value at a
591     *         position is the number of items in that row.
592     * @hide
593     */
594    public int getLayoutNumRows() {
595        return mLayoutNumRows;
596    }
597
598    @Override
599    public boolean dispatchKeyEvent(KeyEvent event) {
600
601        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
602            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
603                removeCallbacks(this);
604                postDelayed(this, ViewConfiguration.getLongPressTimeout());
605            } else if (event.getAction() == KeyEvent.ACTION_UP) {
606
607                if (mMenuBeingLongpressed) {
608                    // It was in cycle mode, so reset it (will also remove us
609                    // from being called back)
610                    setCycleShortcutCaptionMode(false);
611                    return true;
612
613                } else {
614                    // Just remove us from being called back
615                    removeCallbacks(this);
616                    // Fall through to normal processing too
617                }
618            }
619        }
620
621        return super.dispatchKeyEvent(event);
622    }
623
624    @Override
625    protected void onAttachedToWindow() {
626        super.onAttachedToWindow();
627
628        requestFocus();
629    }
630
631    @Override
632    protected void onDetachedFromWindow() {
633        setCycleShortcutCaptionMode(false);
634        super.onDetachedFromWindow();
635    }
636
637    @Override
638    public void onWindowFocusChanged(boolean hasWindowFocus) {
639
640        if (!hasWindowFocus) {
641            setCycleShortcutCaptionMode(false);
642        }
643
644        super.onWindowFocusChanged(hasWindowFocus);
645    }
646
647    /**
648     * Sets the shortcut caption mode for IconMenuView. This mode will
649     * continuously cycle between a child's shortcut and its title.
650     *
651     * @param cycleShortcutAndNormal Whether to go into cycling shortcut mode,
652     *        or to go back to normal.
653     */
654    private void setCycleShortcutCaptionMode(boolean cycleShortcutAndNormal) {
655
656        if (!cycleShortcutAndNormal) {
657            /*
658             * We're setting back to title, so remove any callbacks for setting
659             * to shortcut
660             */
661            removeCallbacks(this);
662            setChildrenCaptionMode(false);
663            mMenuBeingLongpressed = false;
664
665        } else {
666
667            // Set it the first time (the cycle will be started in run()).
668            setChildrenCaptionMode(true);
669        }
670
671    }
672
673    /**
674     * When this method is invoked if the menu is currently not being
675     * longpressed, it means that the longpress has just been reached (so we set
676     * longpress flag, and start cycling). If it is being longpressed, we cycle
677     * to the next mode.
678     */
679    public void run() {
680
681        if (mMenuBeingLongpressed) {
682
683            // Cycle to other caption mode on the children
684            setChildrenCaptionMode(!mLastChildrenCaptionMode);
685
686        } else {
687
688            // Switch ourselves to continuously cycle the items captions
689            mMenuBeingLongpressed = true;
690            setCycleShortcutCaptionMode(true);
691        }
692
693        // We should run again soon to cycle to the other caption mode
694        postDelayed(this, ITEM_CAPTION_CYCLE_DELAY);
695    }
696
697    /**
698     * Iterates children and sets the desired shortcut mode. Only
699     * {@link #setCycleShortcutCaptionMode(boolean)} and {@link #run()} should call
700     * this.
701     *
702     * @param shortcut Whether to show shortcut or the title.
703     */
704    private void setChildrenCaptionMode(boolean shortcut) {
705
706        // Set the last caption mode pushed to children
707        mLastChildrenCaptionMode = shortcut;
708
709        for (int i = getChildCount() - 1; i >= 0; i--) {
710            ((IconMenuItemView) getChildAt(i)).setCaptionMode(shortcut);
711        }
712    }
713
714    /**
715     * For each item, calculates the most dense row that fully shows the item's
716     * title.
717     *
718     * @param width The available width of the icon menu.
719     */
720    private void calculateItemFittingMetadata(int width) {
721        int maxNumItemsPerRow = mMaxItemsPerRow;
722        int numItems = getChildCount();
723        for (int i = 0; i < numItems; i++) {
724            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
725            // Start with 1, since that case does not get covered in the loop below
726            lp.maxNumItemsOnRow = 1;
727            for (int curNumItemsPerRow = maxNumItemsPerRow; curNumItemsPerRow > 0;
728                    curNumItemsPerRow--) {
729                // Check whether this item can fit into a row containing curNumItemsPerRow
730                if (lp.desiredWidth < width / curNumItemsPerRow) {
731                    // It can, mark this value as the most dense row it can fit into
732                    lp.maxNumItemsOnRow = curNumItemsPerRow;
733                    break;
734                }
735            }
736        }
737    }
738
739    @Override
740    protected Parcelable onSaveInstanceState() {
741        Parcelable superState = super.onSaveInstanceState();
742
743        View focusedView = getFocusedChild();
744
745        for (int i = getChildCount() - 1; i >= 0; i--) {
746            if (getChildAt(i) == focusedView) {
747                return new SavedState(superState, i);
748            }
749        }
750
751        return new SavedState(superState, -1);
752    }
753
754    @Override
755    protected void onRestoreInstanceState(Parcelable state) {
756        SavedState ss = (SavedState) state;
757        super.onRestoreInstanceState(ss.getSuperState());
758
759        if (ss.focusedPosition >= getChildCount()) {
760            return;
761        }
762
763        View v = getChildAt(ss.focusedPosition);
764        if (v != null) {
765            v.requestFocus();
766        }
767    }
768
769    private static class SavedState extends BaseSavedState {
770        int focusedPosition;
771
772        /**
773         * Constructor called from {@link IconMenuView#onSaveInstanceState()}
774         */
775        public SavedState(Parcelable superState, int focusedPosition) {
776            super(superState);
777            this.focusedPosition = focusedPosition;
778        }
779
780        /**
781         * Constructor called from {@link #CREATOR}
782         */
783        private SavedState(Parcel in) {
784            super(in);
785            focusedPosition = in.readInt();
786        }
787
788        @Override
789        public void writeToParcel(Parcel dest, int flags) {
790            super.writeToParcel(dest, flags);
791            dest.writeInt(focusedPosition);
792        }
793
794        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
795            public SavedState createFromParcel(Parcel in) {
796                return new SavedState(in);
797            }
798
799            public SavedState[] newArray(int size) {
800                return new SavedState[size];
801            }
802        };
803
804    }
805
806    /**
807     * Layout parameters specific to IconMenuView (stores the left, top, right, bottom from the
808     * measure pass).
809     */
810    public static class LayoutParams extends ViewGroup.MarginLayoutParams
811    {
812        int left, top, right, bottom;
813        int desiredWidth;
814        int maxNumItemsOnRow;
815
816        public LayoutParams(Context c, AttributeSet attrs) {
817            super(c, attrs);
818        }
819
820        public LayoutParams(int width, int height) {
821            super(width, height);
822        }
823    }
824}
825