OSDN Git Service

ff8bff420d202bb5957a7a47dac8ba86396fd30e
[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      * @param Horizontal and vertical spans required
663      */
664     public int[] rectToCell(int width, int height) {
665         // Always assume we're working with the smallest span to make sure we
666         // reserve enough space in both orientations.
667         int actualWidth = mCellWidth + mWidthGap;
668         int actualHeight = mCellHeight + mHeightGap;
669         int smallerSize = Math.min(actualWidth, actualHeight);
670         
671         // Always round up to next largest cell
672         int spanX = (width + smallerSize) / smallerSize;
673         int spanY = (height + smallerSize) / smallerSize;
674         return new int[] { spanX, spanY };
675     }
676
677     /**
678      * Find the first vacant cell, if there is one.
679      *
680      * @param vacant Holds the x and y coordinate of the vacant cell
681      * @param spanX Horizontal cell span.
682      * @param spanY Vertical cell span.
683      * 
684      * @return True if a vacant cell was found
685      */
686     public boolean getVacantCell(int[] vacant, int spanX, int spanY) {
687         final boolean portrait = mPortrait;
688         final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
689         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
690         final boolean[][] occupied = mOccupied;
691
692         findOccupiedCells(xCount, yCount, occupied);
693
694         return findVacantCell(vacant, spanX, spanY, xCount, yCount, occupied);
695     }
696
697     static boolean findVacantCell(int[] vacant, int spanX, int spanY,
698             int xCount, int yCount, boolean[][] occupied) {
699
700         for (int x = 0; x < xCount; x++) {
701             for (int y = 0; y < yCount; y++) {
702                 boolean available = !occupied[x][y];
703 out:            for (int i = x; i < x + spanX - 1 && x < xCount; i++) {
704                     for (int j = y; j < y + spanY - 1 && y < yCount; j++) {
705                         available = available && !occupied[i][j];
706                         if (!available) break out;
707                     }
708                 }
709
710                 if (available) {
711                     vacant[0] = x;
712                     vacant[1] = y;
713                     return true;
714                 }
715             }
716         }
717
718         return false;
719     }
720
721     boolean[] getOccupiedCells() {
722         final boolean portrait = mPortrait;
723         final int xCount = portrait ? mShortAxisCells : mLongAxisCells;
724         final int yCount = portrait ? mLongAxisCells : mShortAxisCells;
725         final boolean[][] occupied = mOccupied;
726
727         findOccupiedCells(xCount, yCount, occupied);
728
729         final boolean[] flat = new boolean[xCount * yCount];
730         for (int y = 0; y < yCount; y++) {
731             for (int x = 0; x < xCount; x++) {
732                 flat[y * xCount + x] = occupied[x][y];
733             }
734         }
735
736         return flat;
737     }
738
739     private void findOccupiedCells(int xCount, int yCount, boolean[][] occupied) {
740         for (int x = 0; x < xCount; x++) {
741             for (int y = 0; y < yCount; y++) {
742                 occupied[x][y] = false;
743             }
744         }
745
746         int count = getChildCount();
747         for (int i = 0; i < count; i++) {
748             View child = getChildAt(i);
749             if (child instanceof Folder) {
750                 continue;
751             }
752             LayoutParams lp = (LayoutParams) child.getLayoutParams();
753
754             for (int x = lp.cellX; x < lp.cellX + lp.cellHSpan && x < xCount; x++) {
755                 for (int y = lp.cellY; y < lp.cellY + lp.cellVSpan && y < yCount; y++) {
756                     occupied[x][y] = true;
757                 }
758             }
759         }
760     }
761
762     @Override
763     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
764         return new CellLayout.LayoutParams(getContext(), attrs);
765     }
766
767     @Override
768     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
769         return p instanceof CellLayout.LayoutParams;
770     }
771
772     @Override
773     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
774         return new CellLayout.LayoutParams(p);
775     }
776
777     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
778         /**
779          * Horizontal location of the item in the grid.
780          */
781         @ViewDebug.ExportedProperty
782         public int cellX;
783
784         /**
785          * Vertical location of the item in the grid.
786          */
787         @ViewDebug.ExportedProperty
788         public int cellY;
789
790         /**
791          * Number of cells spanned horizontally by the item.
792          */
793         @ViewDebug.ExportedProperty
794         public int cellHSpan;
795
796         /**
797          * Number of cells spanned vertically by the item.
798          */
799         @ViewDebug.ExportedProperty
800         public int cellVSpan;
801         
802         /**
803          * Is this item currently being dragged
804          */
805         public boolean isDragging;
806
807         // X coordinate of the view in the layout.
808         @ViewDebug.ExportedProperty
809         int x;
810         // Y coordinate of the view in the layout.
811         @ViewDebug.ExportedProperty
812         int y;
813
814         public LayoutParams(Context c, AttributeSet attrs) {
815             super(c, attrs);
816             cellHSpan = 1;
817             cellVSpan = 1;
818         }
819
820         public LayoutParams(ViewGroup.LayoutParams source) {
821             super(source);
822             cellHSpan = 1;
823             cellVSpan = 1;
824         }
825         
826         public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
827             super(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
828             this.cellX = cellX;
829             this.cellY = cellY;
830             this.cellHSpan = cellHSpan;
831             this.cellVSpan = cellVSpan;
832         }
833
834         public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
835                 int hStartPadding, int vStartPadding) {
836             
837             final int myCellHSpan = cellHSpan;
838             final int myCellVSpan = cellVSpan;
839             final int myCellX = cellX;
840             final int myCellY = cellY;
841             
842             width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
843                     leftMargin - rightMargin;
844             height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
845                     topMargin - bottomMargin;
846
847             x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
848             y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
849         }
850     }
851
852     static final class CellInfo implements ContextMenu.ContextMenuInfo {
853         /**
854          * See View.AttachInfo.InvalidateInfo for futher explanations about
855          * the recycling mechanism. In this case, we recycle the vacant cells
856          * instances because up to several hundreds can be instanciated when
857          * the user long presses an empty cell.
858          */
859         static final class VacantCell {
860             int cellX;
861             int cellY;
862             int spanX;
863             int spanY;
864
865             // We can create up to 523 vacant cells on a 4x4 grid, 100 seems
866             // like a reasonable compromise given the size of a VacantCell and
867             // the fact that the user is not likely to touch an empty 4x4 grid
868             // very often 
869             private static final int POOL_LIMIT = 100;
870             private static final Object sLock = new Object();
871
872             private static int sAcquiredCount = 0;
873             private static VacantCell sRoot;
874
875             private VacantCell next;
876
877             static VacantCell acquire() {
878                 synchronized (sLock) {
879                     if (sRoot == null) {
880                         return new VacantCell();
881                     }
882
883                     VacantCell info = sRoot;
884                     sRoot = info.next;
885                     sAcquiredCount--;
886
887                     return info;
888                 }
889             }
890
891             void release() {
892                 synchronized (sLock) {
893                     if (sAcquiredCount < POOL_LIMIT) {
894                         sAcquiredCount++;
895                         next = sRoot;
896                         sRoot = this;
897                     }
898                 }
899             }
900
901             @Override
902             public String toString() {
903                 return "VacantCell[x=" + cellX + ", y=" + cellY + ", spanX=" + spanX +
904                         ", spanY=" + spanY + "]";
905             }
906         }
907
908         View cell;
909         int cellX;
910         int cellY;
911         int spanX;
912         int spanY;
913         int screen;
914         boolean valid;
915
916         final ArrayList<VacantCell> vacantCells = new ArrayList<VacantCell>(VacantCell.POOL_LIMIT);
917         int maxVacantSpanX;
918         int maxVacantSpanXSpanY;
919         int maxVacantSpanY;
920         int maxVacantSpanYSpanX;
921         final Rect current = new Rect();
922
923         private void clearVacantCells() {
924             final ArrayList<VacantCell> list = vacantCells;
925             final int count = list.size();
926
927             for (int i = 0; i < count; i++) list.get(i).release();
928
929             list.clear();
930         }
931
932         void findVacantCellsFromOccupied(boolean[] occupied, int xCount, int yCount) {
933             if (cellX < 0 || cellY < 0) {
934                 maxVacantSpanX = maxVacantSpanXSpanY = Integer.MIN_VALUE;
935                 maxVacantSpanY = maxVacantSpanYSpanX = Integer.MIN_VALUE;
936                 clearVacantCells();
937                 return;
938             }
939
940             final boolean[][] unflattened = new boolean[xCount][yCount];
941             for (int y = 0; y < yCount; y++) {
942                 for (int x = 0; x < xCount; x++) {
943                     unflattened[x][y] = occupied[y * xCount + x];
944                 }
945             }
946             CellLayout.findIntersectingVacantCells(this, cellX, cellY, xCount, yCount, unflattened);
947         }
948
949         /**
950          * This method can be called only once! Calling #findVacantCellsFromOccupied will
951          * restore the ability to call this method.
952          *
953          * Finds the upper-left coordinate of the first rectangle in the grid that can
954          * hold a cell of the specified dimensions.
955          *
956          * @param cellXY The array that will contain the position of a vacant cell if such a cell
957          *               can be found.
958          * @param spanX The horizontal span of the cell we want to find.
959          * @param spanY The vertical span of the cell we want to find.
960          *
961          * @return True if a vacant cell of the specified dimension was found, false otherwise.
962          */
963         boolean findCellForSpan(int[] cellXY, int spanX, int spanY) {
964             final ArrayList<VacantCell> list = vacantCells;
965             final int count = list.size();
966
967             boolean found = false;
968
969             if (this.spanX >= spanX && this.spanY >= spanY) {
970                 cellXY[0] = cellX;
971                 cellXY[1] = cellY;
972                 found = true;
973             }
974
975             // Look for an exact match first
976             for (int i = 0; i < count; i++) {
977                 VacantCell cell = list.get(i);
978                 if (cell.spanX == spanX && cell.spanY == spanY) {
979                     cellXY[0] = cell.cellX;
980                     cellXY[1] = cell.cellY;
981                     found = true;
982                     break;
983                 }
984             }
985
986             // Look for the first cell large enough
987             for (int i = 0; i < count; i++) {
988                 VacantCell cell = list.get(i);
989                 if (cell.spanX >= spanX && cell.spanY >= spanY) {
990                     cellXY[0] = cell.cellX;
991                     cellXY[1] = cell.cellY;
992                     found = true;
993                     break;
994                 }
995             }
996
997             clearVacantCells();
998
999             return found;
1000         }
1001
1002         @Override
1003         public String toString() {
1004             return "Cell[view=" + (cell == null ? "null" : cell.getClass()) + ", x=" + cellX +
1005                     ", y=" + cellY + "]";
1006         }
1007     }
1008 }
1009
1010