OSDN Git Service

auto import from //branches/cupcake/...@137197
[android-x86/packages-apps-Launcher.git] / src / com / android / launcher / CellLayout.java
1 /*
2  * Copyright (C) 2008 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
17 package com.android.launcher;
18
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Rect;
22 import android.graphics.RectF;
23 import android.util.AttributeSet;
24 import android.view.ContextMenu;
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.view.ViewDebug;
28 import android.view.ViewGroup;
29
30 import java.util.ArrayList;
31
32 public class CellLayout extends ViewGroup {
33     private boolean mPortrait;
34
35     private int mCellWidth;
36     private int mCellHeight;
37     
38     private int mLongAxisStartPadding;
39     private int mLongAxisEndPadding;
40
41     private int mShortAxisStartPadding;
42     private int mShortAxisEndPadding;
43
44     private int mShortAxisCells;
45     private int mLongAxisCells;
46
47     private int mWidthGap;
48     private int mHeightGap;
49
50     private final Rect mRect = new Rect();
51     private final CellInfo mCellInfo = new CellInfo();
52     
53     int[] mCellXY = new int[2];
54     
55     boolean[][] mOccupied;
56
57     private RectF mDragRect = new RectF();
58
59     private boolean mDirtyTag;
60
61     public CellLayout(Context context) {
62         this(context, null);
63     }
64
65     public CellLayout(Context context, AttributeSet attrs) {
66         this(context, attrs, 0);
67     }
68
69     public CellLayout(Context context, AttributeSet attrs, int defStyle) {
70         super(context, attrs, defStyle);
71         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CellLayout, defStyle, 0);
72
73         mCellWidth = a.getDimensionPixelSize(R.styleable.CellLayout_cellWidth, 10);
74         mCellHeight = a.getDimensionPixelSize(R.styleable.CellLayout_cellHeight, 10);
75         
76         mLongAxisStartPadding = 
77             a.getDimensionPixelSize(R.styleable.CellLayout_longAxisStartPadding, 10);
78         mLongAxisEndPadding = 
79             a.getDimensionPixelSize(R.styleable.CellLayout_longAxisEndPadding, 10);
80         mShortAxisStartPadding =
81             a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisStartPadding, 10);
82         mShortAxisEndPadding = 
83             a.getDimensionPixelSize(R.styleable.CellLayout_shortAxisEndPadding, 10);
84         
85         mShortAxisCells = a.getInt(R.styleable.CellLayout_shortAxisCells, 4);
86         mLongAxisCells = a.getInt(R.styleable.CellLayout_longAxisCells, 4);
87
88         a.recycle();
89
90         setAlwaysDrawnWithCacheEnabled(false);
91
92         if (mOccupied == null) {
93             if (mPortrait) {
94                 mOccupied = new boolean[mShortAxisCells][mLongAxisCells];
95             } else {
96                 mOccupied = new boolean[mLongAxisCells][mShortAxisCells];
97             }
98         }
99     }
100
101     int getCountX() {
102         return mPortrait ? mShortAxisCells : mLongAxisCells;
103     }
104
105     int getCountY() {
106         return mPortrait ? mLongAxisCells : mShortAxisCells;
107     }
108
109     @Override
110     public void addView(View child, int index, ViewGroup.LayoutParams params) {
111         // Generate an id for each view, this assumes we have at most 256x256 cells
112         // per workspace screen
113         final LayoutParams cellParams = (LayoutParams) params;
114         child.setId(((getId() & 0xFF) << 16) |
115                 (cellParams.cellX & 0xFF) << 8 | (cellParams.cellY & 0xFF));
116
117         super.addView(child, index, params);
118     }
119
120     @Override
121     public void requestChildFocus(View child, View focused) {
122         super.requestChildFocus(child, focused);
123         if (child != null) {
124             Rect r = new Rect();
125             child.getDrawingRect(r);
126             requestRectangleOnScreen(r);
127         }
128     }
129
130     @Override
131     protected void onAttachedToWindow() {
132         super.onAttachedToWindow();
133         mCellInfo.screen = ((ViewGroup) getParent()).indexOfChild(this);
134     }
135
136     @Override
137     public boolean onInterceptTouchEvent(MotionEvent ev) {
138         final int action = ev.getAction();
139         final CellInfo cellInfo = mCellInfo;
140
141         if (action == MotionEvent.ACTION_DOWN) {
142             final Rect frame = mRect;
143             final int x = (int) ev.getX() + mScrollX;
144             final int y = (int) ev.getY() + mScrollY;
145             final int count = getChildCount();
146
147             boolean found = false;
148             for (int i = count - 1; i >= 0; i--) {
149                 final View child = getChildAt(i);
150
151                 if ((child.getVisibility()) == VISIBLE || child.getAnimation() != null) {
152                     child.getHitRect(frame);
153                     if (frame.contains(x, y)) {
154                         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
155                         cellInfo.cell = child;
156                         cellInfo.cellX = lp.cellX;
157                         cellInfo.cellY = lp.cellY;
158                         cellInfo.spanX = lp.cellHSpan;
159                         cellInfo.spanY = lp.cellVSpan;
160                         cellInfo.valid = true;
161                         found = true;
162                         mDirtyTag = false;
163                         break;
164                     }
165                 }
166             }
167
168             if (!found) {
169                 int cellXY[] = mCellXY;
170                 pointToCellExact(x, y, cellXY);
171
172                 final boolean portrait = mPortrait;
173                 final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
174                 final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
175
176                 final boolean[][] occupied = mOccupied;
177                 findOccupiedCells(xCount, yCount, occupied);
178
179                 cellInfo.cell = null;
180                 cellInfo.cellX = cellXY[0];
181                 cellInfo.cellY = cellXY[1];
182                 cellInfo.spanX = 1;
183                 cellInfo.spanY = 1;
184                 cellInfo.valid = cellXY[0] >= 0 && cellXY[1] >= 0 && cellXY[0] < xCount &&
185                         cellXY[1] < yCount && !occupied[cellXY[0]][cellXY[1]];
186
187                 // Instead of finding the interesting vacant cells here, wait until a
188                 // caller invokes getTag() to retrieve the result. Finding the vacant
189                 // cells is a bit expensive and can generate many new objects, it's
190                 // therefore better to defer it until we know we actually need it.
191
192                 mDirtyTag = true;
193             }
194             setTag(cellInfo);
195         } else if (action == MotionEvent.ACTION_UP) {
196             cellInfo.cell = null;
197             cellInfo.cellX = -1;
198             cellInfo.cellY = -1;
199             cellInfo.spanX = 0;
200             cellInfo.spanY = 0;
201             cellInfo.valid = false;
202             mDirtyTag = false;
203             setTag(cellInfo);
204         }
205
206         return false;
207     }
208
209     @Override
210     public CellInfo getTag() {
211         final CellInfo info = (CellInfo) super.getTag();
212         if (mDirtyTag && info.valid) {
213             final boolean portrait = mPortrait;
214             final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
215             final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
216
217             final boolean[][] occupied = mOccupied;
218             findOccupiedCells(xCount, yCount, occupied);
219
220             findIntersectingVacantCells(info, info.cellX, info.cellY, xCount, yCount, occupied);
221
222             mDirtyTag = false;
223         }
224         return info;
225     }
226
227     private static void findIntersectingVacantCells(CellInfo cellInfo, int x, int y,
228             int xCount, int yCount, boolean[][] occupied) {
229
230         cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
231         cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
232         cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
233         cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
234         cellInfo.clearVacantCells();
235
236         if (occupied[x][y]) {
237             return;
238         }
239
240         cellInfo.current.set(x, y, x, y);
241
242         findVacantCell(cellInfo.current, xCount, yCount, occupied, cellInfo);
243     }
244
245     private static void findVacantCell(Rect current, int xCount, int yCount, boolean[][] occupied,
246             CellInfo cellInfo) {
247
248         addVacantCell(current, cellInfo);
249
250         if (current.left > 0) {
251             if (isColumnEmpty(current.left - 1, current.top, current.bottom, occupied)) {
252                 current.left--;
253                 findVacantCell(current, xCount, yCount, occupied, cellInfo);
254                 current.left++;
255             }
256         }
257
258         if (current.right < xCount - 1) {
259             if (isColumnEmpty(current.right + 1, current.top, current.bottom, occupied)) {
260                 current.right++;
261                 findVacantCell(current, xCount, yCount, occupied, cellInfo);
262                 current.right--;
263             }
264         }
265
266         if (current.top > 0) {
267             if (isRowEmpty(current.top - 1, current.left, current.right, occupied)) {
268                 current.top--;
269                 findVacantCell(current, xCount, yCount, occupied, cellInfo);
270                 current.top++;
271             }
272         }
273
274         if (current.bottom < yCount - 1) {
275             if (isRowEmpty(current.bottom + 1, current.left, current.right, occupied)) {
276                 current.bottom++;
277                 findVacantCell(current, xCount, yCount, occupied, cellInfo);
278                 current.bottom--;
279             }
280         }
281     }
282
283     private static void addVacantCell(Rect current, CellInfo cellInfo) {
284         CellInfo.VacantCell cell = CellInfo.VacantCell.acquire();
285         cell.cellX = current.left;
286         cell.cellY = current.top;
287         cell.spanX = current.right - current.left + 1;
288         cell.spanY = current.bottom - current.top + 1;
289         if (cell.spanX > cellInfo.maxVacantSpanX) {
290             cellInfo.maxVacantSpanX = cell.spanX;
291             cellInfo.maxVacantSpanXSpanY = cell.spanY;
292         }
293         if (cell.spanY > cellInfo.maxVacantSpanY) {
294             cellInfo.maxVacantSpanY = cell.spanY;
295             cellInfo.maxVacantSpanYSpanX = cell.spanX;
296         }
297         cellInfo.vacantCells.add(cell);
298     }
299
300     private static boolean isColumnEmpty(int x, int top, int bottom, boolean[][] occupied) {
301         for (int y = top; y <= bottom; y++) {
302             if (occupied[x][y]) {
303                 return false;
304             }
305         }
306         return true;
307     }
308
309     private static boolean isRowEmpty(int y, int left, int right, boolean[][] occupied) {
310         for (int x = left; x <= right; x++) {
311             if (occupied[x][y]) {
312                 return false;
313             }
314         }
315         return true;
316     }
317
318     CellInfo findAllVacantCells(boolean[] occupiedCells) {
319         final boolean portrait = mPortrait;
320         final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
321         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
322
323         boolean[][] occupied = mOccupied;
324
325         if (occupiedCells != null) {
326             for (int y = 0; y < yCount; y++) {
327                 for (int x = 0; x < xCount; x++) {
328                     occupied[x][y] = occupiedCells[y * xCount + x];
329                 }
330             }
331         } else {
332             findOccupiedCells(xCount, yCount, occupied);
333         }
334
335         CellInfo cellInfo = new CellInfo();
336
337         cellInfo.cellX = -1;
338         cellInfo.cellY = -1;
339         cellInfo.spanY = 0;
340         cellInfo.spanX = 0;
341         cellInfo.maxVacantSpanX = Integer.MIN_VALUE;
342         cellInfo.maxVacantSpanXSpanY = Integer.MIN_VALUE;
343         cellInfo.maxVacantSpanY = Integer.MIN_VALUE;
344         cellInfo.maxVacantSpanYSpanX = Integer.MIN_VALUE;
345         cellInfo.screen = mCellInfo.screen;
346
347         Rect current = cellInfo.current;
348
349         for (int x = 0; x < xCount; x++) {
350             for (int y = 0; y < yCount; y++) {
351                 if (!occupied[x][y]) {
352                     current.set(x, y, x, y);
353                     findVacantCell(current, xCount, yCount, occupied, cellInfo);
354                     occupied[x][y] = true;
355                 }
356             }
357         }
358
359         cellInfo.valid = cellInfo.vacantCells.size() > 0;
360
361         // Assume the caller will perform their own cell searching, otherwise we
362         // risk causing an unnecessary rebuild after findCellForSpan()
363         
364         return cellInfo;
365     }
366
367     /**
368      * Given a point, return the cell that strictly encloses that point 
369      * @param x X coordinate of the point
370      * @param y Y coordinate of the point
371      * @param result Array of 2 ints to hold the x and y coordinate of the cell
372      */
373     void pointToCellExact(int x, int y, int[] result) {
374         final boolean portrait = mPortrait;
375         
376         final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
377         final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
378
379         result[0] = (x - hStartPadding) / (mCellWidth + mWidthGap);
380         result[1] = (y - vStartPadding) / (mCellHeight + mHeightGap);
381
382         final int xAxis = portrait ? mShortAxisCells : mLongAxisCells;
383         final int yAxis = portrait ? mLongAxisCells : mShortAxisCells;
384
385         if (result[0] < 0) result[0] = 0;
386         if (result[0] >= xAxis) result[0] = xAxis - 1;
387         if (result[1] < 0) result[1] = 0;
388         if (result[1] >= yAxis) result[1] = yAxis - 1;
389     }
390     
391     /**
392      * Given a point, return the cell that most closely encloses that point
393      * @param x X coordinate of the point
394      * @param y Y coordinate of the point
395      * @param result Array of 2 ints to hold the x and y coordinate of the cell
396      */
397     void pointToCellRounded(int x, int y, int[] result) {
398         pointToCellExact(x + (mCellWidth / 2), y + (mCellHeight / 2), result);
399     }
400
401     /**
402      * Given a cell coordinate, return the point that represents the upper left corner of that cell
403      * 
404      * @param cellX X coordinate of the cell 
405      * @param cellY Y coordinate of the cell
406      * 
407      * @param result Array of 2 ints to hold the x and y coordinate of the point
408      */
409     void cellToPoint(int cellX, int cellY, int[] result) {
410         final boolean portrait = mPortrait;
411         
412         final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
413         final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
414
415
416         result[0] = hStartPadding + cellX * (mCellWidth + mWidthGap);
417         result[1] = vStartPadding + cellY * (mCellHeight + mHeightGap);
418     }
419
420     @Override
421     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
422         // TODO: currently ignoring padding
423         
424         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
425         int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);
426         
427         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
428         int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
429         
430         if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
431             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
432         }
433
434         final int shortAxisCells = mShortAxisCells;
435         final int longAxisCells = mLongAxisCells;
436         final int longAxisStartPadding = mLongAxisStartPadding;
437         final int longAxisEndPadding = mLongAxisEndPadding;
438         final int shortAxisStartPadding = mShortAxisStartPadding;
439         final int shortAxisEndPadding = mShortAxisEndPadding;
440         final int cellWidth = mCellWidth;
441         final int cellHeight = mCellHeight;
442
443         mPortrait = heightSpecSize > widthSpecSize;
444
445         int numShortGaps = shortAxisCells - 1;
446         int numLongGaps = longAxisCells - 1;
447
448         if (mPortrait) {
449             int vSpaceLeft = heightSpecSize - longAxisStartPadding - longAxisEndPadding
450                     - (cellHeight * longAxisCells);
451             mHeightGap = vSpaceLeft / numLongGaps;
452
453             int hSpaceLeft = widthSpecSize - shortAxisStartPadding - shortAxisEndPadding
454                     - (cellWidth * shortAxisCells);
455             if (numShortGaps > 0) {
456                 mWidthGap = hSpaceLeft / numShortGaps;
457             } else {
458                 mWidthGap = 0;
459             }
460         } else {
461             int hSpaceLeft = widthSpecSize - longAxisStartPadding - longAxisEndPadding
462                     - (cellWidth * longAxisCells);
463             mWidthGap = hSpaceLeft / numLongGaps;
464
465             int vSpaceLeft = heightSpecSize - shortAxisStartPadding - shortAxisEndPadding
466                     - (cellHeight * shortAxisCells);
467             if (numShortGaps > 0) {
468                 mHeightGap = vSpaceLeft / numShortGaps;
469             } else {
470                 mHeightGap = 0;
471             }
472         }
473         
474         int count = getChildCount();
475
476         for (int i = 0; i < count; i++) {
477             View child = getChildAt(i);
478             LayoutParams lp = (LayoutParams) child.getLayoutParams();
479
480             if (mPortrait) {
481                 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, shortAxisStartPadding,
482                         longAxisStartPadding);
483             } else {
484                 lp.setup(cellWidth, cellHeight, mWidthGap, mHeightGap, longAxisStartPadding,
485                         shortAxisStartPadding);
486             }
487
488             int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
489             int childheightMeasureSpec =
490                     MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
491             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
492         }
493
494         setMeasuredDimension(widthSpecSize, heightSpecSize);
495     }
496
497     @Override
498     protected void onLayout(boolean changed, int l, int t, int r, int b) {
499         int count = getChildCount();
500
501         for (int i = 0; i < count; i++) {
502             View child = getChildAt(i);
503             if (child.getVisibility() != GONE) {
504
505                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
506
507                 int childLeft = lp.x;
508                 int childTop = lp.y;
509                 child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
510             }
511         }
512     }
513
514     @Override
515     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
516         final int count = getChildCount();
517         for (int i = 0; i < count; i++) {
518             final View view = getChildAt(i);
519             view.setDrawingCacheEnabled(enabled);
520             // Update the drawing caches
521             view.buildDrawingCache();
522         }
523     }
524
525     @Override
526     protected void setChildrenDrawnWithCacheEnabled(boolean enabled) {
527         super.setChildrenDrawnWithCacheEnabled(enabled);
528     }
529
530     boolean acceptChildDrop(int x, int y, int cellHSpan, int cellVSpan, View cell) {
531         int[] cellXY = mCellXY;
532         pointToCellRounded(x, y, cellXY);
533         int cellX = cellXY[0];
534         int cellY = cellXY[1];
535
536         return findCell(cellX, cellY, cellHSpan, cellVSpan, cell) == null;
537     }
538
539     /**
540      * Finds the first View intersecting with the specified cell. If the cell is outside
541      * of the layout, this is returned.
542      *
543      * @param cellX The X location of the cell to test.
544      * @param cellY The Y location of the cell to test.
545      * @param cellHSpan The horizontal span of the cell to test.
546      * @param cellVSpan The vertical span of the cell to test.
547      * @param ignoreCell View to ignore during the test.
548      *
549      * @return Returns the first View intersecting with the specified cell, this if the cell
550      *         lies outside of this layout's grid or null if no View was found.
551      */
552     View findCell(int cellX, int cellY, int cellHSpan, int cellVSpan, View ignoreCell) {
553         if (cellX < 0 || cellX + cellHSpan > (mPortrait ? mShortAxisCells : mLongAxisCells) ||
554                 cellY < 0 || cellY + cellVSpan > (mPortrait ? mLongAxisCells : mShortAxisCells)) {
555             return this;
556         }
557
558         final int count = getChildCount();
559         for (int i = 0; i < count; i++) {
560             final View view = getChildAt(i);
561             if (view == ignoreCell) {
562                 continue;
563             }
564
565             final LayoutParams lp = (LayoutParams) view.getLayoutParams();
566             if (cellX < lp.cellX + lp.cellHSpan && lp.cellX < cellX + cellHSpan &&
567                     cellY < lp.cellY + lp.cellVSpan && lp.cellY < cellY + cellVSpan) {
568                 return view;
569             }
570         }
571
572         return null;
573     }
574
575     /**
576      * Drop a child at the specified position
577      *
578      * @param child The child that is being dropped
579      * @param cellX The child's new x location
580      * @param cellY The child's new y location
581      */
582     void onDropChild(View child, int cellX, int cellY) {
583         int[] cellXY = mCellXY;
584         pointToCellRounded(cellX, cellY, cellXY);
585         LayoutParams lp = (LayoutParams) child.getLayoutParams();
586         lp.cellX = cellXY[0];
587         lp.cellY = cellXY[1];
588         lp.isDragging = false;
589         mDragRect.setEmpty();
590         child.requestLayout();
591         invalidate();
592     }
593
594     void onDropAborted(View child) {
595         if (child != null) {
596             ((LayoutParams) child.getLayoutParams()).isDragging = false;
597             invalidate();
598         }
599         mDragRect.setEmpty();
600     }
601
602     /**
603      * Start dragging the specified child
604      * 
605      * @param child The child that is being dragged
606      */
607     void onDragChild(View child) {
608         LayoutParams lp = (LayoutParams) child.getLayoutParams();
609         lp.isDragging = true;
610         mDragRect.setEmpty();
611     }
612     
613     /**
614      * Drag a child over the specified position
615      * 
616      * @param child The child that is being dropped
617      * @param cellX The child's new x cell location
618      * @param cellY The child's new y cell location 
619      */
620     void onDragOverChild(View child, int cellX, int cellY) {
621         int[] cellXY = mCellXY;
622         pointToCellRounded(cellX, cellY, cellXY);
623         LayoutParams lp = (LayoutParams) child.getLayoutParams();
624         cellToRect(cellXY[0], cellXY[1], lp.cellHSpan, lp.cellVSpan, mDragRect);
625         invalidate();
626     }
627     
628     /**
629      * Computes a bounding rectangle for a range of cells
630      *  
631      * @param cellX X coordinate of upper left corner expressed as a cell position
632      * @param cellY Y coordinate of upper left corner expressed as a cell position
633      * @param cellHSpan Width in cells 
634      * @param cellVSpan Height in cells
635      * @param dragRect Rectnagle into which to put the results
636      */
637     public void cellToRect(int cellX, int cellY, int cellHSpan, int cellVSpan, RectF dragRect) {
638         final boolean portrait = mPortrait;
639         final int cellWidth = mCellWidth;
640         final int cellHeight = mCellHeight;
641         final int widthGap = mWidthGap;
642         final int heightGap = mHeightGap;
643         
644         final int hStartPadding = portrait ? mShortAxisStartPadding : mLongAxisStartPadding;
645         final int vStartPadding = portrait ? mLongAxisStartPadding : mShortAxisStartPadding;
646         
647         int width = cellHSpan * cellWidth + ((cellHSpan - 1) * widthGap);
648         int height = cellVSpan * cellHeight + ((cellVSpan - 1) * heightGap);
649
650         int x = hStartPadding + cellX * (cellWidth + widthGap);
651         int y = vStartPadding + cellY * (cellHeight + heightGap);
652         
653         dragRect.set(x, y, x + width, y + height);
654     }
655     
656     /**
657      * Computes the required horizontal and vertical cell spans to always 
658      * fit the given rectangle.
659      *  
660      * @param width Width in pixels
661      * @param height Height in pixels
662      */
663     public int[] rectToCell(int width, int height) {
664         // Always assume we're working with the smallest span to make sure we
665         // reserve enough space in both orientations.
666         int actualWidth = mCellWidth + mWidthGap;
667         int actualHeight = mCellHeight + mHeightGap;
668         int smallerSize = Math.min(actualWidth, actualHeight);
669         
670         // Always round up to next largest cell
671         int spanX = (width + smallerSize) / smallerSize;
672         int spanY = (height + smallerSize) / smallerSize;
673         return new int[] { spanX, spanY };
674     }
675
676     /**
677      * Find the first vacant cell, if there is one.
678      *
679      * @param vacant Holds the x and y coordinate of the vacant cell
680      * @param spanX Horizontal cell span.
681      * @param spanY Vertical cell span.
682      * 
683      * @return True if a vacant cell was found
684      */
685     public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
686         final boolean portrait = mPortrait;
687         final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
688         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
689         final boolean[][] occupied = mOccupied;
690
691         findOccupiedCells(xCount, yCount, occupied);
692
693         return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
694     }
695
696     static boolean findVacantCell(int[] vacant, int spanX, int spanY,
697             int xCount, int yCount, boolean[][] occupied) {
698
699         for (int x = 0; x < xCount; x++) {
700             for (int y = 0; y < yCount; y++) {
701                 boolean available = !occupied[x][y];
702 out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
703                     for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
704                         available = available && !occupied[i][j];
705                         if (!available) break out;
706                     }
707                 }
708
709                 if (available) {
710                     vacant[0] = x;
711                     vacant[1] = y;
712                     return true;
713                 }
714             }
715         }
716
717         return false;
718     }
719
720     boolean[] getOccupiedCells() {
721         final boolean portrait = mPortrait;
722         final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
723         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
724         final boolean[][] occupied = mOccupied;
725
726         findOccupiedCells(xCount, yCount, occupied);
727
728         final boolean[] flat = new boolean[xCount * yCount];
729         for (int y = 0; y < yCount; y++) {
730             for (int x = 0; x < xCount; x++) {
731                 flat[y * xCount + x] = occupied[x][y];
732             }
733         }
734
735         return flat;
736     }
737
738     private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied) {
739         for (int x = 0; x < xCount; x++) {
740             for (int y = 0; y < yCount; y++) {
741                 occupied[x][y] = false;
742             }
743         }
744
745         int count = getChildCount();
746         for (int i = 0; i < count; i++) {
747             View child = getChildAt(i);
748             if (child instanceof Folder) {
749                 continue;
750             }
751             LayoutParams lp = (LayoutParams) child.getLayoutParams();
752
753             for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
754                 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
755                     occupied[x][y] = true;
756                 }
757             }
758         }
759     }
760
761     @Override
762     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
763         return new CellLayout.LayoutParams(getContext(), attrs);
764     }
765
766     @Override
767     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
768         return p instanceof CellLayout.LayoutParams;
769     }
770
771     @Override
772     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
773         return new CellLayout.LayoutParams(p);
774     }
775
776     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
777         /**
778          * Horizontal location of the item in the grid.
779          */
780         @ViewDebug.ExportedProperty
781         public int cellX;
782
783         /**
784          * Vertical location of the item in the grid.
785          */
786         @ViewDebug.ExportedProperty
787         public int cellY;
788
789         /**
790          * Number of cells spanned horizontally by the item.
791          */
792         @ViewDebug.ExportedProperty
793         public int cellHSpan;
794
795         /**
796          * Number of cells spanned vertically by the item.
797          */
798         @ViewDebug.ExportedProperty
799         public int cellVSpan;
800         
801         /**
802          * Is this item currently being dragged
803          */
804         public boolean isDragging;
805
806         // X coordinate of the view in the layout.
807         @ViewDebug.ExportedProperty
808         int x;
809         // Y coordinate of the view in the layout.
810         @ViewDebug.ExportedProperty
811         int y;
812
813         public LayoutParams(Context c, AttributeSet attrs) {
814             super(c, attrs);
815             cellHSpan = 1;
816             cellVSpan = 1;
817         }
818
819         public LayoutParams(ViewGroup.LayoutParams source) {
820             super(source);
821             cellHSpan = 1;
822             cellVSpan = 1;
823         }
824         
825         public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
826             super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
827             this.cellX = cellX;
828             this.cellY = cellY;
829             this.cellHSpan = cellHSpan;
830             this.cellVSpan = cellVSpan;
831         }
832
833         public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
834                 int hStartPadding, int vStartPadding) {
835             
836             final int myCellHSpan = cellHSpan;
837             final int myCellVSpan = cellVSpan;
838             final int myCellX = cellX;
839             final int myCellY = cellY;
840             
841             width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
842                     leftMargin - rightMargin;
843             height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
844                     topMargin - bottomMargin;
845
846             x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
847             y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
848         }
849     }
850
851     static final class CellInfo implements ContextMenu.ContextMenuInfo {
852         /**
853          * See View.AttachInfo.InvalidateInfo for futher explanations about
854          * the recycling mechanism. In this case, we recycle the vacant cells
855          * instances because up to several hundreds can be instanciated when
856          * the user long presses an empty cell.
857          */
858         static final class VacantCell {
859             int cellX;
860             int cellY;
861             int spanX;
862             int spanY;
863
864             // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
865             // like a reasonable compromise given the size of a VacantCell and
866             // the fact that the user is not likely to touch an empty 4x4 grid
867             // very often 
868             private static final int POOL_LIMIT = 100;
869             private static final Object sLock = new Object();
870
871             private static int sAcquiredCount = 0;
872             private static VacantCell sRoot;
873
874             private VacantCell next;
875
876             static VacantCell acquire() {
877                 synchronized (sLock) {
878                     if (sRoot == null) {
879                         return new VacantCell();
880                     }
881
882                     VacantCell info = sRoot;
883                     sRoot = info.next;
884                     sAcquiredCount--;
885
886                     return info;
887                 }
888             }
889
890             void release() {
891                 synchronized (sLock) {
892                     if (sAcquiredCount < POOL_LIMIT) {
893                         sAcquiredCount++;
894                         next = sRoot;
895                         sRoot = this;
896                     }
897                 }
898             }
899
900             @Override
901             public String toString() {
902                 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
903                         ", spanY=" + spanY + "]";
904             }
905         }
906
907         View cell;
908         int cellX;
909         int cellY;
910         int spanX;
911         int spanY;
912         int screen;
913         boolean valid;
914
915         final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
916         int maxVacantSpanX;
917         int maxVacantSpanXSpanY;
918         int maxVacantSpanY;
919         int maxVacantSpanYSpanX;
920         final Rect current = new Rect();
921
922         private void clearVacantCells() {
923             final ArrayList<VacantCell> list = vacantCells;
924             final int count = list.size();
925
926             for (int i = 0; i < count; i++) list.get(i).release();
927
928             list.clear();
929         }
930
931         void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
932             if (cellX < 0 || cellY < 0) {
933                 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
934                 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
935                 clearVacantCells();
936                 return;
937             }
938
939             final boolean[][] unflattened = new boolean[xCount][yCount];
940             for (int y = 0; y < yCount; y++) {
941                 for (int x = 0; x < xCount; x++) {
942                     unflattened[x][y] = occupied[y * xCount + x];
943                 }
944             }
945             CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
946         }
947
948         /**
949          * This method can be called only once! Calling #findVacantCellsFromOccupied will
950          * restore the ability to call this method.
951          *
952          * Finds the upper-left coordinate of the first rectangle in the grid that can
953          * hold a cell of the specified dimensions.
954          *
955          * @param cellXY The array that will contain the position of a vacant cell if such a cell
956          *               can be found.
957          * @param spanX The horizontal span of the cell we want to find.
958          * @param spanY The vertical span of the cell we want to find.
959          *
960          * @return True if a vacant cell of the specified dimension was found, false otherwise.
961          */
962         boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
963             final ArrayList<VacantCell> list = vacantCells;
964             final int count = list.size();
965
966             boolean found = false;
967
968             if (this.spanX >= spanX && this.spanY >= spanY) {
969                 cellXY[0] = cellX;
970                 cellXY[1] = cellY;
971                 found = true;
972             }
973
974             // Look for an exact match first
975             for (int i = 0; i < count; i++) {
976                 VacantCell cell = list.get(i);
977                 if (cell.spanX == spanX && cell.spanY == spanY) {
978                     cellXY[0] = cell.cellX;
979                     cellXY[1] = cell.cellY;
980                     found = true;
981                     break;
982                 }
983             }
984
985             // Look for the first cell large enough
986             for (int i = 0; i < count; i++) {
987                 VacantCell cell = list.get(i);
988                 if (cell.spanX >= spanX && cell.spanY >= spanY) {
989                     cellXY[0] = cell.cellX;
990                     cellXY[1] = cell.cellY;
991                     found = true;
992                     break;
993                 }
994             }
995
996             clearVacantCells();
997
998             return found;
999         }
1000
1001         @Override
1002         public String toString() {
1003             return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
1004                     ", y=" + cellY + "]";
1005         }
1006     }
1007 }
1008
1009