IconMenuView.java revision 843ef36f7b96cc19ea7d2996b7c8661b41ec3452
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
183        // Start with the least possible number of rows
184        int curNumRows =
185                Math.min((int) Math.ceil(numItems / (float) mMaxItemsPerRow), mMaxRows);
186
187        /*
188         * Increase the number of rows until we find a configuration that fits
189         * all of the items' titles. Worst case, we use mMaxRows.
190         */
191        for (; curNumRows <= mMaxRows; curNumRows++) {
192            layoutItemsUsingGravity(curNumRows, numItems);
193
194            if (curNumRows >= numItems) {
195                // Can't have more rows than items
196                break;
197            }
198
199            if (doItemsFit()) {
200                // All the items fit, so this is a good configuration
201                break;
202            }
203        }
204    }
205
206    /**
207     * Figures out the layout for the menu items by equally distributing, and
208     * adding any excess items equally to lower rows.
209     *
210     * @param numRows The total number of rows for the menu view
211     * @param numItems The total number of items (across all rows) contained in
212     *            the menu view
213     * @return int[] Where the value of index i contains the number of items for row i
214     */
215    private void layoutItemsUsingGravity(int numRows, int numItems) {
216        int numBaseItemsPerRow = numItems / numRows;
217        int numLeftoverItems = numItems % numRows;
218        /**
219         * The bottom rows will each get a leftover item. Rows (indexed at 0)
220         * that are >= this get a leftover item. Note: if there are 0 leftover
221         * items, no rows will get them since this value will be greater than
222         * the last row.
223         */
224        int rowsThatGetALeftoverItem = numRows - numLeftoverItems;
225
226        int[] layout = mLayout;
227        for (int i = 0; i < numRows; i++) {
228            layout[i] = numBaseItemsPerRow;
229
230            // Fill the bottom rows with a leftover item each
231            if (i >= rowsThatGetALeftoverItem) {
232                layout[i]++;
233            }
234        }
235
236        mLayoutNumRows = numRows;
237    }
238
239    /**
240     * Checks whether each item's title is fully visible using the current
241     * layout.
242     *
243     * @return True if the items fit (each item's text is fully visible), false
244     *         otherwise.
245     */
246    private boolean doItemsFit() {
247        int itemPos = 0;
248
249        int[] layout = mLayout;
250        int numRows = mLayoutNumRows;
251        for (int row = 0; row < numRows; row++) {
252            int numItemsOnRow = layout[row];
253
254            /*
255             * If there is only one item on this row, increasing the
256             * number of rows won't help.
257             */
258            if (numItemsOnRow == 1) {
259                itemPos++;
260                continue;
261            }
262
263            for (int itemsOnRowCounter = numItemsOnRow; itemsOnRowCounter > 0;
264                    itemsOnRowCounter--) {
265                View child = getChildAt(itemPos++);
266                LayoutParams lp = (LayoutParams) child.getLayoutParams();
267                if (lp.maxNumItemsOnRow < numItemsOnRow) {
268                    return false;
269                }
270            }
271        }
272
273        return true;
274    }
275
276    /**
277     * Adds an IconMenuItemView to this icon menu view.
278     * @param itemView The item's view to add
279     */
280    private void addItemView(IconMenuItemView itemView) {
281        // Set ourselves on the item view
282        itemView.setIconMenuView(this);
283
284        // Apply the background to the item view
285        itemView.setBackgroundDrawable(mItemBackground.getConstantState().newDrawable());
286
287        // This class is the invoker for all its item views
288        itemView.setItemInvoker(this);
289
290        addView(itemView, itemView.getTextAppropriateLayoutParams());
291    }
292
293    /**
294     * Creates the item view for the 'More' button which is used to switch to
295     * the expanded menu view. This button is a special case since it does not
296     * have a MenuItemData backing it.
297     * @return The IconMenuItemView for the 'More' button
298     */
299    private IconMenuItemView createMoreItemView() {
300        LayoutInflater inflater = mMenu.getMenuType(MenuBuilder.TYPE_ICON).getInflater();
301
302        final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate(
303                com.android.internal.R.layout.icon_menu_item_layout, null);
304
305        Resources r = getContext().getResources();
306        itemView.initialize(r.getText(com.android.internal.R.string.more_item_label), mMoreIcon);
307
308        // Set up a click listener on the view since there will be no invocation sequence
309        // due to the lack of a MenuItemData this view
310        itemView.setOnClickListener(new OnClickListener() {
311            public void onClick(View v) {
312                // Switches the menu to expanded mode
313                MenuBuilder.Callback cb = mMenu.getCallback();
314                if (cb != null) {
315                    // Call callback
316                    cb.onMenuModeChange(mMenu);
317                }
318            }
319        });
320
321        return itemView;
322    }
323
324
325    public void initialize(MenuBuilder menu, int menuType) {
326        mMenu = menu;
327        updateChildren(true);
328    }
329
330    public void updateChildren(boolean cleared) {
331        // This method does a clear refresh of children
332        removeAllViews();
333
334        final ArrayList<MenuItemImpl> itemsToShow = mMenu.getVisibleItems();
335        final int numItems = itemsToShow.size();
336        final int numItemsThatCanFit = mMaxItems;
337        // Minimum of the num that can fit and the num that we have
338        final int minFitMinus1AndNumItems = Math.min(numItemsThatCanFit - 1, numItems);
339
340        MenuItemImpl itemData;
341        // Traverse through all but the last item that can fit since that last item can either
342        // be a 'More' button or a sixth item
343        for (int i = 0; i < minFitMinus1AndNumItems; i++) {
344            itemData = itemsToShow.get(i);
345            addItemView((IconMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ICON, this));
346        }
347
348        if (numItems > numItemsThatCanFit) {
349            // If there are more items than we can fit, show the 'More' button to
350            // switch to expanded mode
351            if (mMoreItemView == null) {
352                mMoreItemView = createMoreItemView();
353            }
354
355            addItemView(mMoreItemView);
356
357            // The last view is the more button, so the actual number of items is one less than
358            // the number that can fit
359            mNumActualItemsShown = numItemsThatCanFit - 1;
360        } else if (numItems == numItemsThatCanFit) {
361            // There are exactly the number we can show, so show the last item
362            final MenuItemImpl lastItemData = itemsToShow.get(numItemsThatCanFit - 1);
363            addItemView((IconMenuItemView) lastItemData.getItemView(MenuBuilder.TYPE_ICON, this));
364
365            // The items shown fit exactly
366            mNumActualItemsShown = numItemsThatCanFit;
367        }
368    }
369
370    /**
371     * The positioning algorithm that gets called from onMeasure.  It
372     * just computes positions for each child, and then stores them in the child's layout params.
373     * @param menuWidth The width of this menu to assume for positioning
374     * @param menuHeight The height of this menu to assume for positioning
375     */
376    private void positionChildren(int menuWidth, int menuHeight) {
377        // Clear the containers for the positions where the dividers should be drawn
378        if (mHorizontalDivider != null) mHorizontalDividerRects.clear();
379        if (mVerticalDivider != null) mVerticalDividerRects.clear();
380
381        // Get the minimum number of rows needed
382        final int numRows = mLayoutNumRows;
383        final int numRowsMinus1 = numRows - 1;
384        final int numItemsForRow[] = mLayout;
385
386        // The item position across all rows
387        int itemPos = 0;
388        View child;
389        IconMenuView.LayoutParams childLayoutParams = null;
390
391        // Use float for this to get precise positions (uniform item widths
392        // instead of last one taking any slack), and then convert to ints at last opportunity
393        float itemLeft;
394        float itemTop = 0;
395        // Since each row can have a different number of items, this will be computed per row
396        float itemWidth;
397        // Subtract the space needed for the horizontal dividers
398        final float itemHeight = (menuHeight - mHorizontalDividerHeight * (numRows - 1))
399                / (float)numRows;
400
401        for (int row = 0; row < numRows; row++) {
402            // Start at the left
403            itemLeft = 0;
404
405            // Subtract the space needed for the vertical dividers, and divide by the number of items
406            itemWidth = (menuWidth - mVerticalDividerWidth * (numItemsForRow[row] - 1))
407                    / (float)numItemsForRow[row];
408
409            for (int itemPosOnRow = 0; itemPosOnRow < numItemsForRow[row]; itemPosOnRow++) {
410                // Tell the child to be exactly this size
411                child = getChildAt(itemPos);
412                child.measure(MeasureSpec.makeMeasureSpec((int) itemWidth, MeasureSpec.EXACTLY),
413                        MeasureSpec.makeMeasureSpec((int) itemHeight, MeasureSpec.EXACTLY));
414
415                // Remember the child's position for layout
416                childLayoutParams = (IconMenuView.LayoutParams) child.getLayoutParams();
417                childLayoutParams.left = (int) itemLeft;
418                childLayoutParams.right = (int) (itemLeft + itemWidth);
419                childLayoutParams.top = (int) itemTop;
420                childLayoutParams.bottom = (int) (itemTop + itemHeight);
421
422                // Increment by item width
423                itemLeft += itemWidth;
424                itemPos++;
425
426                // Add a vertical divider to draw
427                if (mVerticalDivider != null) {
428                    mVerticalDividerRects.add(new Rect((int) itemLeft,
429                            (int) itemTop, (int) (itemLeft + mVerticalDividerWidth),
430                            (int) (itemTop + itemHeight)));
431                }
432
433                // Increment by divider width (even if we're not computing
434                // dividers, since we need to leave room for them when
435                // calculating item positions)
436                itemLeft += mVerticalDividerWidth;
437            }
438
439            // Last child on each row should extend to very right edge
440            if (childLayoutParams != null) {
441                childLayoutParams.right = menuWidth;
442            }
443
444            itemTop += itemHeight;
445
446            // Add a horizontal divider to draw
447            if ((mHorizontalDivider != null) && (row < numRowsMinus1)) {
448                mHorizontalDividerRects.add(new Rect(0, (int) itemTop, menuWidth,
449                        (int) (itemTop + mHorizontalDividerHeight)));
450
451                itemTop += mHorizontalDividerHeight;
452            }
453        }
454    }
455
456    @Override
457    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
458        if (mHasStaleChildren) {
459            mHasStaleChildren = false;
460
461            // If we have stale data, resync with the menu
462            updateChildren(false);
463        }
464
465        int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec);
466        calculateItemFittingMetadata(measuredWidth);
467        layoutItems(measuredWidth);
468
469        // Get the desired height of the icon menu view (last row of items does
470        // not have a divider below)
471        final int desiredHeight = (mRowHeight + mHorizontalDividerHeight) *
472                mLayoutNumRows - mHorizontalDividerHeight;
473
474        // Maximum possible width and desired height
475        setMeasuredDimension(measuredWidth,
476                resolveSize(desiredHeight, heightMeasureSpec));
477
478        // Position the children
479        positionChildren(mMeasuredWidth, mMeasuredHeight);
480    }
481
482
483    @Override
484    protected void onLayout(boolean changed, int l, int t, int r, int b) {
485        View child;
486        IconMenuView.LayoutParams childLayoutParams;
487
488        for (int i = getChildCount() - 1; i >= 0; i--) {
489            child = getChildAt(i);
490            childLayoutParams = (IconMenuView.LayoutParams)child
491                    .getLayoutParams();
492
493            // Layout children according to positions set during the measure
494            child.layout(childLayoutParams.left, childLayoutParams.top, childLayoutParams.right,
495                    childLayoutParams.bottom);
496        }
497    }
498
499    @Override
500    protected void onDraw(Canvas canvas) {
501        Drawable drawable = mHorizontalDivider;
502        if (drawable != null) {
503            // If we have a horizontal divider to draw, draw it at the remembered positions
504            final ArrayList<Rect> rects = mHorizontalDividerRects;
505            for (int i = rects.size() - 1; i >= 0; i--) {
506                drawable.setBounds(rects.get(i));
507                drawable.draw(canvas);
508            }
509        }
510
511        drawable = mVerticalDivider;
512        if (drawable != null) {
513            // If we have a vertical divider to draw, draw it at the remembered positions
514            final ArrayList<Rect> rects = mVerticalDividerRects;
515            for (int i = rects.size() - 1; i >= 0; i--) {
516                drawable.setBounds(rects.get(i));
517                drawable.draw(canvas);
518            }
519        }
520    }
521
522    public boolean invokeItem(MenuItemImpl item) {
523        return mMenu.performItemAction(item, 0);
524    }
525
526    @Override
527    public LayoutParams generateLayoutParams(AttributeSet attrs) {
528        return new IconMenuView.LayoutParams(getContext(), attrs);
529    }
530
531    @Override
532    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
533        // Override to allow type-checking of LayoutParams.
534        return p instanceof IconMenuView.LayoutParams;
535    }
536
537    /**
538     * Marks as having stale children.
539     */
540    void markStaleChildren() {
541        if (!mHasStaleChildren) {
542            mHasStaleChildren = true;
543            requestLayout();
544        }
545    }
546
547    /**
548     * @return The number of actual items shown (those that are backed by an
549     *         {@link MenuView.ItemView} implementation--eg: excludes More
550     *         item).
551     */
552    int getNumActualItemsShown() {
553        return mNumActualItemsShown;
554    }
555
556
557    public int getWindowAnimations() {
558        return mAnimations;
559    }
560
561    /**
562     * Returns the number of items per row.
563     * <p>
564     * This should only be used for testing.
565     *
566     * @return The length of the array is the number of rows. A value at a
567     *         position is the number of items in that row.
568     * @hide
569     */
570    public int[] getLayout() {
571        return mLayout;
572    }
573
574    /**
575     * Returns the number of rows in the layout.
576     * <p>
577     * This should only be used for testing.
578     *
579     * @return The length of the array is the number of rows. A value at a
580     *         position is the number of items in that row.
581     * @hide
582     */
583    public int getLayoutNumRows() {
584        return mLayoutNumRows;
585    }
586
587    @Override
588    public boolean dispatchKeyEvent(KeyEvent event) {
589
590        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
591            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
592                removeCallbacks(this);
593                postDelayed(this, ViewConfiguration.getLongPressTimeout());
594            } else if (event.getAction() == KeyEvent.ACTION_UP) {
595
596                if (mMenuBeingLongpressed) {
597                    // It was in cycle mode, so reset it (will also remove us
598                    // from being called back)
599                    setCycleShortcutCaptionMode(false);
600                    return true;
601
602                } else {
603                    // Just remove us from being called back
604                    removeCallbacks(this);
605                    // Fall through to normal processing too
606                }
607            }
608        }
609
610        return super.dispatchKeyEvent(event);
611    }
612
613    @Override
614    protected void onAttachedToWindow() {
615        super.onAttachedToWindow();
616
617        requestFocus();
618    }
619
620    @Override
621    protected void onDetachedFromWindow() {
622        setCycleShortcutCaptionMode(false);
623        super.onDetachedFromWindow();
624    }
625
626    @Override
627    public void onWindowFocusChanged(boolean hasWindowFocus) {
628
629        if (!hasWindowFocus) {
630            setCycleShortcutCaptionMode(false);
631        }
632
633        super.onWindowFocusChanged(hasWindowFocus);
634    }
635
636    /**
637     * Sets the shortcut caption mode for IconMenuView. This mode will
638     * continuously cycle between a child's shortcut and its title.
639     *
640     * @param cycleShortcutAndNormal Whether to go into cycling shortcut mode,
641     *        or to go back to normal.
642     */
643    private void setCycleShortcutCaptionMode(boolean cycleShortcutAndNormal) {
644
645        if (!cycleShortcutAndNormal) {
646            /*
647             * We're setting back to title, so remove any callbacks for setting
648             * to shortcut
649             */
650            removeCallbacks(this);
651            setChildrenCaptionMode(false);
652            mMenuBeingLongpressed = false;
653
654        } else {
655
656            // Set it the first time (the cycle will be started in run()).
657            setChildrenCaptionMode(true);
658        }
659
660    }
661
662    /**
663     * When this method is invoked if the menu is currently not being
664     * longpressed, it means that the longpress has just been reached (so we set
665     * longpress flag, and start cycling). If it is being longpressed, we cycle
666     * to the next mode.
667     */
668    public void run() {
669
670        if (mMenuBeingLongpressed) {
671
672            // Cycle to other caption mode on the children
673            setChildrenCaptionMode(!mLastChildrenCaptionMode);
674
675        } else {
676
677            // Switch ourselves to continuously cycle the items captions
678            mMenuBeingLongpressed = true;
679            setCycleShortcutCaptionMode(true);
680        }
681
682        // We should run again soon to cycle to the other caption mode
683        postDelayed(this, ITEM_CAPTION_CYCLE_DELAY);
684    }
685
686    /**
687     * Iterates children and sets the desired shortcut mode. Only
688     * {@link #setCycleShortcutCaptionMode(boolean)} and {@link #run()} should call
689     * this.
690     *
691     * @param shortcut Whether to show shortcut or the title.
692     */
693    private void setChildrenCaptionMode(boolean shortcut) {
694
695        // Set the last caption mode pushed to children
696        mLastChildrenCaptionMode = shortcut;
697
698        for (int i = getChildCount() - 1; i >= 0; i--) {
699            ((IconMenuItemView) getChildAt(i)).setCaptionMode(shortcut);
700        }
701    }
702
703    /**
704     * For each item, calculates the most dense row that fully shows the item's
705     * title.
706     *
707     * @param width The available width of the icon menu.
708     */
709    private void calculateItemFittingMetadata(int width) {
710        int maxNumItemsPerRow = mMaxItemsPerRow;
711        int numItems = getChildCount();
712        for (int i = 0; i < numItems; i++) {
713            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
714            // Start with 1, since that case does not get covered in the loop below
715            lp.maxNumItemsOnRow = 1;
716            for (int curNumItemsPerRow = maxNumItemsPerRow; curNumItemsPerRow > 0;
717                    curNumItemsPerRow--) {
718                // Check whether this item can fit into a row containing curNumItemsPerRow
719                if (lp.desiredWidth < width / curNumItemsPerRow) {
720                    // It can, mark this value as the most dense row it can fit into
721                    lp.maxNumItemsOnRow = curNumItemsPerRow;
722                    break;
723                }
724            }
725        }
726    }
727
728    @Override
729    protected Parcelable onSaveInstanceState() {
730        Parcelable superState = super.onSaveInstanceState();
731
732        View focusedView = getFocusedChild();
733
734        for (int i = getChildCount() - 1; i >= 0; i--) {
735            if (getChildAt(i) == focusedView) {
736                return new SavedState(superState, i);
737            }
738        }
739
740        return new SavedState(superState, -1);
741    }
742
743    @Override
744    protected void onRestoreInstanceState(Parcelable state) {
745        SavedState ss = (SavedState) state;
746        super.onRestoreInstanceState(ss.getSuperState());
747
748        if (ss.focusedPosition >= getChildCount()) {
749            return;
750        }
751
752        View v = getChildAt(ss.focusedPosition);
753        if (v != null) {
754            v.requestFocus();
755        }
756    }
757
758    private static class SavedState extends BaseSavedState {
759        int focusedPosition;
760
761        /**
762         * Constructor called from {@link IconMenuView#onSaveInstanceState()}
763         */
764        public SavedState(Parcelable superState, int focusedPosition) {
765            super(superState);
766            this.focusedPosition = focusedPosition;
767        }
768
769        /**
770         * Constructor called from {@link #CREATOR}
771         */
772        private SavedState(Parcel in) {
773            super(in);
774            focusedPosition = in.readInt();
775        }
776
777        @Override
778        public void writeToParcel(Parcel dest, int flags) {
779            super.writeToParcel(dest, flags);
780            dest.writeInt(focusedPosition);
781        }
782
783        public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
784            public SavedState createFromParcel(Parcel in) {
785                return new SavedState(in);
786            }
787
788            public SavedState[] newArray(int size) {
789                return new SavedState[size];
790            }
791        };
792
793    }
794
795    /**
796     * Layout parameters specific to IconMenuView (stores the left, top, right, bottom from the
797     * measure pass).
798     */
799    public static class LayoutParams extends ViewGroup.MarginLayoutParams
800    {
801        int left, top, right, bottom;
802        int desiredWidth;
803        int maxNumItemsOnRow;
804
805        public LayoutParams(Context c, AttributeSet attrs) {
806            super(c, attrs);
807        }
808
809        public LayoutParams(int width, int height) {
810            super(width, height);
811        }
812    }
813}
814