Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 package org.mozilla.gecko.home;
8 import java.util.EnumSet;
10 import org.mozilla.gecko.R;
11 import org.mozilla.gecko.Telemetry;
12 import org.mozilla.gecko.TelemetryContract;
13 import org.mozilla.gecko.ThumbnailHelper;
14 import org.mozilla.gecko.db.BrowserDB.URLColumns;
15 import org.mozilla.gecko.db.TopSitesCursorWrapper;
16 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
18 import android.content.Context;
19 import android.content.res.TypedArray;
20 import android.database.Cursor;
21 import android.graphics.Rect;
22 import android.text.TextUtils;
23 import android.util.AttributeSet;
24 import android.view.ContextMenu.ContextMenuInfo;
25 import android.view.View;
26 import android.widget.AbsListView;
27 import android.widget.AdapterView;
28 import android.widget.GridView;
30 /**
31 * A grid view of top and pinned sites.
32 * Each cell in the grid is a TopSitesGridItemView.
33 */
34 public class TopSitesGridView extends GridView {
35 private static final String LOGTAG = "GeckoTopSitesGridView";
37 // Listener for editing pinned sites.
38 public static interface OnEditPinnedSiteListener {
39 public void onEditPinnedSite(int position, String searchTerm);
40 }
42 // Max number of top sites that needs to be shown.
43 private final int mMaxSites;
45 // Number of columns to show.
46 private final int mNumColumns;
48 // Horizontal spacing in between the rows.
49 private final int mHorizontalSpacing;
51 // Vertical spacing in between the rows.
52 private final int mVerticalSpacing;
54 // Measured width of this view.
55 private int mMeasuredWidth;
57 // Measured height of this view.
58 private int mMeasuredHeight;
60 // On URL open listener.
61 private OnUrlOpenListener mUrlOpenListener;
63 // Edit pinned site listener.
64 private OnEditPinnedSiteListener mEditPinnedSiteListener;
66 // Context menu info.
67 private TopSitesGridContextMenuInfo mContextMenuInfo;
69 // Whether we're handling focus changes or not. This is used
70 // to avoid infinite re-layouts when using this GridView as
71 // a ListView header view (see bug 918044).
72 private boolean mIsHandlingFocusChange;
74 public TopSitesGridView(Context context) {
75 this(context, null);
76 }
78 public TopSitesGridView(Context context, AttributeSet attrs) {
79 this(context, attrs, R.attr.topSitesGridViewStyle);
80 }
82 public TopSitesGridView(Context context, AttributeSet attrs, int defStyle) {
83 super(context, attrs, defStyle);
84 mMaxSites = getResources().getInteger(R.integer.number_of_top_sites);
85 mNumColumns = getResources().getInteger(R.integer.number_of_top_sites_cols);
86 setNumColumns(mNumColumns);
88 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopSitesGridView, defStyle, 0);
89 mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_horizontalSpacing, 0x00);
90 mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_verticalSpacing, 0x00);
91 a.recycle();
93 mIsHandlingFocusChange = false;
94 }
96 /**
97 * {@inheritDoc}
98 */
99 @Override
100 public void onAttachedToWindow() {
101 super.onAttachedToWindow();
103 setOnItemClickListener(new AdapterView.OnItemClickListener() {
104 @Override
105 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
106 TopSitesGridItemView row = (TopSitesGridItemView) view;
108 // Decode "user-entered" URLs before loading them.
109 String url = HomeFragment.decodeUserEnteredUrl(row.getUrl());
111 // If the url is empty, the user can pin a site.
112 // If not, navigate to the page given by the url.
113 if (!TextUtils.isEmpty(url)) {
114 if (mUrlOpenListener != null) {
115 Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.GRID_ITEM, Integer.toString(position));
117 mUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
118 }
119 } else {
120 if (mEditPinnedSiteListener != null) {
121 mEditPinnedSiteListener.onEditPinnedSite(position, "");
122 }
123 }
124 }
125 });
127 setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
128 @Override
129 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
130 Cursor cursor = (Cursor) parent.getItemAtPosition(position);
132 TopSitesGridItemView gridView = (TopSitesGridItemView) view;
133 if (cursor == null || gridView.isEmpty()) {
134 mContextMenuInfo = null;
135 return false;
136 }
138 mContextMenuInfo = new TopSitesGridContextMenuInfo(view, position, id);
139 updateContextMenuFromCursor(mContextMenuInfo, cursor);
140 return showContextMenuForChild(TopSitesGridView.this);
141 }
142 });
143 }
145 @Override
146 public void onDetachedFromWindow() {
147 super.onDetachedFromWindow();
149 mUrlOpenListener = null;
150 mEditPinnedSiteListener = null;
151 }
153 @Override
154 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
155 mIsHandlingFocusChange = true;
156 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
157 mIsHandlingFocusChange = false;
158 }
160 @Override
161 public void requestLayout() {
162 if (!mIsHandlingFocusChange) {
163 super.requestLayout();
164 }
165 }
167 /**
168 * {@inheritDoc}
169 */
170 @Override
171 public int getColumnWidth() {
172 // This method will be called from onMeasure() too.
173 // It's better to use getMeasuredWidth(), as it is safe in this case.
174 final int totalHorizontalSpacing = mNumColumns > 0 ? (mNumColumns - 1) * mHorizontalSpacing : 0;
175 return (getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - totalHorizontalSpacing) / mNumColumns;
176 }
178 /**
179 * {@inheritDoc}
180 */
181 @Override
182 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
183 // Sets the padding for this view.
184 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
186 final int measuredWidth = getMeasuredWidth();
187 if (measuredWidth == mMeasuredWidth) {
188 // Return the cached values as the width is the same.
189 setMeasuredDimension(mMeasuredWidth, mMeasuredHeight);
190 return;
191 }
193 final int columnWidth = getColumnWidth();
195 // Get the first child from the adapter.
196 final TopSitesGridItemView child = new TopSitesGridItemView(getContext());
198 // Set a default LayoutParams on the child, if it doesn't have one on its own.
199 AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams();
200 if (params == null) {
201 params = new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT,
202 AbsListView.LayoutParams.WRAP_CONTENT);
203 child.setLayoutParams(params);
204 }
206 // Measure the exact width of the child, and the height based on the width.
207 // Note: the child (and TopSitesThumbnailView) takes care of calculating its height.
208 int childWidthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY);
209 int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
210 child.measure(childWidthSpec, childHeightSpec);
211 final int childHeight = child.getMeasuredHeight();
213 // This is the maximum width of the contents of each child in the grid.
214 // Use this as the target width for thumbnails.
215 final int thumbnailWidth = child.getMeasuredWidth() - child.getPaddingLeft() - child.getPaddingRight();
216 ThumbnailHelper.getInstance().setThumbnailWidth(thumbnailWidth);
218 // Number of rows required to show these top sites.
219 final int rows = (int) Math.ceil((double) mMaxSites / mNumColumns);
220 final int childrenHeight = childHeight * rows;
221 final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0;
223 // Total height of this view.
224 final int measuredHeight = childrenHeight + getPaddingTop() + getPaddingBottom() + totalVerticalSpacing;
225 setMeasuredDimension(measuredWidth, measuredHeight);
226 mMeasuredWidth = measuredWidth;
227 mMeasuredHeight = measuredHeight;
228 }
230 @Override
231 public ContextMenuInfo getContextMenuInfo() {
232 return mContextMenuInfo;
233 }
235 /*
236 * Update the fields of a TopSitesGridContextMenuInfo object
237 * from a cursor.
238 *
239 * @param info context menu info object to be updated
240 * @param cursor used to update the context menu info object
241 */
242 private void updateContextMenuFromCursor(TopSitesGridContextMenuInfo info, Cursor cursor) {
243 info.url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
244 info.title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE));
245 info.isPinned = ((TopSitesCursorWrapper) cursor).isPinned();
246 }
247 /**
248 * Set an url open listener to be used by this view.
249 *
250 * @param listener An url open listener for this view.
251 */
252 public void setOnUrlOpenListener(OnUrlOpenListener listener) {
253 mUrlOpenListener = listener;
254 }
256 /**
257 * Set an edit pinned site listener to be used by this view.
258 *
259 * @param listener An edit pinned site listener for this view.
260 */
261 public void setOnEditPinnedSiteListener(final OnEditPinnedSiteListener listener) {
262 mEditPinnedSiteListener = listener;
263 }
265 /**
266 * Stores information regarding the creation of the context menu for a GridView item.
267 */
268 public static class TopSitesGridContextMenuInfo extends HomeContextMenuInfo {
269 public boolean isPinned = false;
271 public TopSitesGridContextMenuInfo(View targetView, int position, long id) {
272 super(targetView, position, id);
273 }
274 }
275 }