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