1/*
2 * Copyright (C) 2010 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.launcher2;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.util.AttributeSet;
22import android.view.MotionEvent;
23import android.view.View;
24import android.view.ViewDebug;
25import android.view.ViewGroup;
26
27import com.android.launcher.R;
28
29/**
30 * An abstraction of the original CellLayout which supports laying out items
31 * which span multiple cells into a grid-like layout.  Also supports dimming
32 * to give a preview of its contents.
33 */
34public class PagedViewCellLayout extends ViewGroup implements Page {
35    static final String TAG = "PagedViewCellLayout";
36
37    private int mCellCountX;
38    private int mCellCountY;
39    private int mOriginalCellWidth;
40    private int mOriginalCellHeight;
41    private int mCellWidth;
42    private int mCellHeight;
43    private int mOriginalWidthGap;
44    private int mOriginalHeightGap;
45    private int mWidthGap;
46    private int mHeightGap;
47    private int mMaxGap;
48    protected PagedViewCellLayoutChildren mChildren;
49
50    public PagedViewCellLayout(Context context) {
51        this(context, null);
52    }
53
54    public PagedViewCellLayout(Context context, AttributeSet attrs) {
55        this(context, attrs, 0);
56    }
57
58    public PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle) {
59        super(context, attrs, defStyle);
60
61        setAlwaysDrawnWithCacheEnabled(false);
62
63        // setup default cell parameters
64        Resources resources = context.getResources();
65        mOriginalCellWidth = mCellWidth =
66            resources.getDimensionPixelSize(R.dimen.apps_customize_cell_width);
67        mOriginalCellHeight = mCellHeight =
68            resources.getDimensionPixelSize(R.dimen.apps_customize_cell_height);
69        mCellCountX = LauncherModel.getCellCountX();
70        mCellCountY = LauncherModel.getCellCountY();
71        mOriginalWidthGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1;
72        mMaxGap = resources.getDimensionPixelSize(R.dimen.apps_customize_max_gap);
73
74        mChildren = new PagedViewCellLayoutChildren(context);
75        mChildren.setCellDimensions(mCellWidth, mCellHeight);
76        mChildren.setGap(mWidthGap, mHeightGap);
77
78        addView(mChildren);
79    }
80
81    public int getCellWidth() {
82        return mCellWidth;
83    }
84
85    public int getCellHeight() {
86        return mCellHeight;
87    }
88
89    @Override
90    public void cancelLongPress() {
91        super.cancelLongPress();
92
93        // Cancel long press for all children
94        final int count = getChildCount();
95        for (int i = 0; i < count; i++) {
96            final View child = getChildAt(i);
97            child.cancelLongPress();
98        }
99    }
100
101    public boolean addViewToCellLayout(View child, int index, int childId,
102            PagedViewCellLayout.LayoutParams params) {
103        final PagedViewCellLayout.LayoutParams lp = params;
104
105        // Generate an id for each view, this assumes we have at most 256x256 cells
106        // per workspace screen
107        if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) &&
108                lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) {
109            // If the horizontal or vertical span is set to -1, it is taken to
110            // mean that it spans the extent of the CellLayout
111            if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX;
112            if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY;
113
114            child.setId(childId);
115            mChildren.addView(child, index, lp);
116
117            return true;
118        }
119        return false;
120    }
121
122    @Override
123    public void removeAllViewsOnPage() {
124        mChildren.removeAllViews();
125        setLayerType(LAYER_TYPE_NONE, null);
126    }
127
128    @Override
129    public void removeViewOnPageAt(int index) {
130        mChildren.removeViewAt(index);
131    }
132
133    /**
134     * Clears all the key listeners for the individual icons.
135     */
136    public void resetChildrenOnKeyListeners() {
137        int childCount = mChildren.getChildCount();
138        for (int j = 0; j < childCount; ++j) {
139            mChildren.getChildAt(j).setOnKeyListener(null);
140        }
141    }
142
143    @Override
144    public int getPageChildCount() {
145        return mChildren.getChildCount();
146    }
147
148    public PagedViewCellLayoutChildren getChildrenLayout() {
149        return mChildren;
150    }
151
152    @Override
153    public View getChildOnPageAt(int i) {
154        return mChildren.getChildAt(i);
155    }
156
157    @Override
158    public int indexOfChildOnPage(View v) {
159        return mChildren.indexOfChild(v);
160    }
161
162    public int getCellCountX() {
163        return mCellCountX;
164    }
165
166    public int getCellCountY() {
167        return mCellCountY;
168    }
169
170    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
171        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
172        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
173
174        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
175        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
176
177        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
178            throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
179        }
180
181        int numWidthGaps = mCellCountX - 1;
182        int numHeightGaps = mCellCountY - 1;
183
184        if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
185            int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
186            int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
187            int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth);
188            int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight);
189            mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
190            mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
191
192            mChildren.setGap(mWidthGap, mHeightGap);
193        } else {
194            mWidthGap = mOriginalWidthGap;
195            mHeightGap = mOriginalHeightGap;
196        }
197
198        // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
199        int newWidth = widthSpecSize;
200        int newHeight = heightSpecSize;
201        if (widthSpecMode == MeasureSpec.AT_MOST) {
202            newWidth = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) +
203                ((mCellCountX - 1) * mWidthGap);
204            newHeight = getPaddingTop() + getPaddingBottom() + (mCellCountY * mCellHeight) +
205                ((mCellCountY - 1) * mHeightGap);
206            setMeasuredDimension(newWidth, newHeight);
207        }
208
209        final int count = getChildCount();
210        for (int i = 0; i < count; i++) {
211            View child = getChildAt(i);
212            int childWidthMeasureSpec =
213                MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
214                        getPaddingRight(), MeasureSpec.EXACTLY);
215            int childheightMeasureSpec =
216                MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
217                        getPaddingBottom(), MeasureSpec.EXACTLY);
218            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
219        }
220
221        setMeasuredDimension(newWidth, newHeight);
222    }
223
224    int getContentWidth() {
225        return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight();
226    }
227
228    int getContentHeight() {
229        if (mCellCountY > 0) {
230            return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap);
231        }
232        return 0;
233    }
234
235    int getWidthBeforeFirstLayout() {
236        if (mCellCountX > 0) {
237            return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap);
238        }
239        return 0;
240    }
241
242    @Override
243    protected void onLayout(boolean changed, int l, int t, int r, int b) {
244        int count = getChildCount();
245        for (int i = 0; i < count; i++) {
246            View child = getChildAt(i);
247            child.layout(getPaddingLeft(), getPaddingTop(),
248                r - l - getPaddingRight(), b - t - getPaddingBottom());
249        }
250    }
251
252    @Override
253    public boolean onTouchEvent(MotionEvent event) {
254        boolean result = super.onTouchEvent(event);
255        int count = getPageChildCount();
256        if (count > 0) {
257            // We only intercept the touch if we are tapping in empty space after the final row
258            View child = getChildOnPageAt(count - 1);
259            int bottom = child.getBottom();
260            int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX());
261            if (numRows < getCellCountY()) {
262                // Add a little bit of buffer if there is room for another row
263                bottom += mCellHeight / 2;
264            }
265            result = result || (event.getY() < bottom);
266        }
267        return result;
268    }
269
270    public void enableCenteredContent(boolean enabled) {
271        mChildren.enableCenteredContent(enabled);
272    }
273
274    @Override
275    protected void setChildrenDrawingCacheEnabled(boolean enabled) {
276        mChildren.setChildrenDrawingCacheEnabled(enabled);
277    }
278
279    public void setCellCount(int xCount, int yCount) {
280        mCellCountX = xCount;
281        mCellCountY = yCount;
282        requestLayout();
283    }
284
285    public void setGap(int widthGap, int heightGap) {
286        mOriginalWidthGap = mWidthGap = widthGap;
287        mOriginalHeightGap = mHeightGap = heightGap;
288        mChildren.setGap(widthGap, heightGap);
289    }
290
291    public int[] getCellCountForDimensions(int width, int height) {
292        // Always assume we're working with the smallest span to make sure we
293        // reserve enough space in both orientations
294        int smallerSize = Math.min(mCellWidth, mCellHeight);
295
296        // Always round up to next largest cell
297        int spanX = (width + smallerSize) / smallerSize;
298        int spanY = (height + smallerSize) / smallerSize;
299
300        return new int[] { spanX, spanY };
301    }
302
303    /**
304     * Start dragging the specified child
305     *
306     * @param child The child that is being dragged
307     */
308    void onDragChild(View child) {
309        PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
310        lp.isDragging = true;
311    }
312
313    /**
314     * Estimates the number of cells that the specified width would take up.
315     */
316    public int estimateCellHSpan(int width) {
317        // We don't show the next/previous pages any more, so we use the full width, minus the
318        // padding
319        int availWidth = width - (getPaddingLeft() + getPaddingRight());
320
321        // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N
322        int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap));
323
324        // We don't do anything fancy to determine if we squeeze another row in.
325        return n;
326    }
327
328    /**
329     * Estimates the number of cells that the specified height would take up.
330     */
331    public int estimateCellVSpan(int height) {
332        // The space for a page is the height - top padding (current page) - bottom padding (current
333        // page)
334        int availHeight = height - (getPaddingTop() + getPaddingBottom());
335
336        // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N
337        int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap));
338
339        // We don't do anything fancy to determine if we squeeze another row in.
340        return n;
341    }
342
343    /** Returns an estimated center position of the cell at the specified index */
344    public int[] estimateCellPosition(int x, int y) {
345        return new int[] {
346                getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2),
347                getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2)
348        };
349    }
350
351    public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) {
352        mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width));
353        mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height));
354        requestLayout();
355    }
356
357    /**
358     * Estimates the width that the number of hSpan cells will take up.
359     */
360    public int estimateCellWidth(int hSpan) {
361        // TODO: we need to take widthGap into effect
362        return hSpan * mCellWidth;
363    }
364
365    /**
366     * Estimates the height that the number of vSpan cells will take up.
367     */
368    public int estimateCellHeight(int vSpan) {
369        // TODO: we need to take heightGap into effect
370        return vSpan * mCellHeight;
371    }
372
373    @Override
374    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
375        return new PagedViewCellLayout.LayoutParams(getContext(), attrs);
376    }
377
378    @Override
379    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
380        return p instanceof PagedViewCellLayout.LayoutParams;
381    }
382
383    @Override
384    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
385        return new PagedViewCellLayout.LayoutParams(p);
386    }
387
388    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
389        /**
390         * Horizontal location of the item in the grid.
391         */
392        @ViewDebug.ExportedProperty
393        public int cellX;
394
395        /**
396         * Vertical location of the item in the grid.
397         */
398        @ViewDebug.ExportedProperty
399        public int cellY;
400
401        /**
402         * Number of cells spanned horizontally by the item.
403         */
404        @ViewDebug.ExportedProperty
405        public int cellHSpan;
406
407        /**
408         * Number of cells spanned vertically by the item.
409         */
410        @ViewDebug.ExportedProperty
411        public int cellVSpan;
412
413        /**
414         * Is this item currently being dragged
415         */
416        public boolean isDragging;
417
418        // a data object that you can bind to this layout params
419        private Object mTag;
420
421        // X coordinate of the view in the layout.
422        @ViewDebug.ExportedProperty
423        int x;
424        // Y coordinate of the view in the layout.
425        @ViewDebug.ExportedProperty
426        int y;
427
428        public LayoutParams() {
429            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
430            cellHSpan = 1;
431            cellVSpan = 1;
432        }
433
434        public LayoutParams(Context c, AttributeSet attrs) {
435            super(c, attrs);
436            cellHSpan = 1;
437            cellVSpan = 1;
438        }
439
440        public LayoutParams(ViewGroup.LayoutParams source) {
441            super(source);
442            cellHSpan = 1;
443            cellVSpan = 1;
444        }
445
446        public LayoutParams(LayoutParams source) {
447            super(source);
448            this.cellX = source.cellX;
449            this.cellY = source.cellY;
450            this.cellHSpan = source.cellHSpan;
451            this.cellVSpan = source.cellVSpan;
452        }
453
454        public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
455            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
456            this.cellX = cellX;
457            this.cellY = cellY;
458            this.cellHSpan = cellHSpan;
459            this.cellVSpan = cellVSpan;
460        }
461
462        public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
463                int hStartPadding, int vStartPadding) {
464
465            final int myCellHSpan = cellHSpan;
466            final int myCellVSpan = cellVSpan;
467            final int myCellX = cellX;
468            final int myCellY = cellY;
469
470            width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
471                    leftMargin - rightMargin;
472            height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
473                    topMargin - bottomMargin;
474
475            if (LauncherApplication.isScreenLarge()) {
476                x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
477                y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
478            } else {
479                x = myCellX * (cellWidth + widthGap) + leftMargin;
480                y = myCellY * (cellHeight + heightGap) + topMargin;
481            }
482        }
483
484        public Object getTag() {
485            return mTag;
486        }
487
488        public void setTag(Object tag) {
489            mTag = tag;
490        }
491
492        public String toString() {
493            return "(" + this.cellX + ", " + this.cellY + ", " +
494                this.cellHSpan + ", " + this.cellVSpan + ")";
495        }
496    }
497}
498
499interface Page {
500    public int getPageChildCount();
501    public View getChildOnPageAt(int i);
502    public void removeAllViewsOnPage();
503    public void removeViewOnPageAt(int i);
504    public int indexOfChildOnPage(View v);
505}
506