1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/home/TopSitesGridView.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,275 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.home; 1.10 + 1.11 +import java.util.EnumSet; 1.12 + 1.13 +import org.mozilla.gecko.R; 1.14 +import org.mozilla.gecko.Telemetry; 1.15 +import org.mozilla.gecko.TelemetryContract; 1.16 +import org.mozilla.gecko.ThumbnailHelper; 1.17 +import org.mozilla.gecko.db.BrowserDB.URLColumns; 1.18 +import org.mozilla.gecko.db.TopSitesCursorWrapper; 1.19 +import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; 1.20 + 1.21 +import android.content.Context; 1.22 +import android.content.res.TypedArray; 1.23 +import android.database.Cursor; 1.24 +import android.graphics.Rect; 1.25 +import android.text.TextUtils; 1.26 +import android.util.AttributeSet; 1.27 +import android.view.ContextMenu.ContextMenuInfo; 1.28 +import android.view.View; 1.29 +import android.widget.AbsListView; 1.30 +import android.widget.AdapterView; 1.31 +import android.widget.GridView; 1.32 + 1.33 +/** 1.34 + * A grid view of top and pinned sites. 1.35 + * Each cell in the grid is a TopSitesGridItemView. 1.36 + */ 1.37 +public class TopSitesGridView extends GridView { 1.38 + private static final String LOGTAG = "GeckoTopSitesGridView"; 1.39 + 1.40 + // Listener for editing pinned sites. 1.41 + public static interface OnEditPinnedSiteListener { 1.42 + public void onEditPinnedSite(int position, String searchTerm); 1.43 + } 1.44 + 1.45 + // Max number of top sites that needs to be shown. 1.46 + private final int mMaxSites; 1.47 + 1.48 + // Number of columns to show. 1.49 + private final int mNumColumns; 1.50 + 1.51 + // Horizontal spacing in between the rows. 1.52 + private final int mHorizontalSpacing; 1.53 + 1.54 + // Vertical spacing in between the rows. 1.55 + private final int mVerticalSpacing; 1.56 + 1.57 + // Measured width of this view. 1.58 + private int mMeasuredWidth; 1.59 + 1.60 + // Measured height of this view. 1.61 + private int mMeasuredHeight; 1.62 + 1.63 + // On URL open listener. 1.64 + private OnUrlOpenListener mUrlOpenListener; 1.65 + 1.66 + // Edit pinned site listener. 1.67 + private OnEditPinnedSiteListener mEditPinnedSiteListener; 1.68 + 1.69 + // Context menu info. 1.70 + private TopSitesGridContextMenuInfo mContextMenuInfo; 1.71 + 1.72 + // Whether we're handling focus changes or not. This is used 1.73 + // to avoid infinite re-layouts when using this GridView as 1.74 + // a ListView header view (see bug 918044). 1.75 + private boolean mIsHandlingFocusChange; 1.76 + 1.77 + public TopSitesGridView(Context context) { 1.78 + this(context, null); 1.79 + } 1.80 + 1.81 + public TopSitesGridView(Context context, AttributeSet attrs) { 1.82 + this(context, attrs, R.attr.topSitesGridViewStyle); 1.83 + } 1.84 + 1.85 + public TopSitesGridView(Context context, AttributeSet attrs, int defStyle) { 1.86 + super(context, attrs, defStyle); 1.87 + mMaxSites = getResources().getInteger(R.integer.number_of_top_sites); 1.88 + mNumColumns = getResources().getInteger(R.integer.number_of_top_sites_cols); 1.89 + setNumColumns(mNumColumns); 1.90 + 1.91 + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TopSitesGridView, defStyle, 0); 1.92 + mHorizontalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_horizontalSpacing, 0x00); 1.93 + mVerticalSpacing = a.getDimensionPixelOffset(R.styleable.TopSitesGridView_android_verticalSpacing, 0x00); 1.94 + a.recycle(); 1.95 + 1.96 + mIsHandlingFocusChange = false; 1.97 + } 1.98 + 1.99 + /** 1.100 + * {@inheritDoc} 1.101 + */ 1.102 + @Override 1.103 + public void onAttachedToWindow() { 1.104 + super.onAttachedToWindow(); 1.105 + 1.106 + setOnItemClickListener(new AdapterView.OnItemClickListener() { 1.107 + @Override 1.108 + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1.109 + TopSitesGridItemView row = (TopSitesGridItemView) view; 1.110 + 1.111 + // Decode "user-entered" URLs before loading them. 1.112 + String url = HomeFragment.decodeUserEnteredUrl(row.getUrl()); 1.113 + 1.114 + // If the url is empty, the user can pin a site. 1.115 + // If not, navigate to the page given by the url. 1.116 + if (!TextUtils.isEmpty(url)) { 1.117 + if (mUrlOpenListener != null) { 1.118 + Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.GRID_ITEM, Integer.toString(position)); 1.119 + 1.120 + mUrlOpenListener.onUrlOpen(url, EnumSet.noneOf(OnUrlOpenListener.Flags.class)); 1.121 + } 1.122 + } else { 1.123 + if (mEditPinnedSiteListener != null) { 1.124 + mEditPinnedSiteListener.onEditPinnedSite(position, ""); 1.125 + } 1.126 + } 1.127 + } 1.128 + }); 1.129 + 1.130 + setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { 1.131 + @Override 1.132 + public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 1.133 + Cursor cursor = (Cursor) parent.getItemAtPosition(position); 1.134 + 1.135 + TopSitesGridItemView gridView = (TopSitesGridItemView) view; 1.136 + if (cursor == null || gridView.isEmpty()) { 1.137 + mContextMenuInfo = null; 1.138 + return false; 1.139 + } 1.140 + 1.141 + mContextMenuInfo = new TopSitesGridContextMenuInfo(view, position, id); 1.142 + updateContextMenuFromCursor(mContextMenuInfo, cursor); 1.143 + return showContextMenuForChild(TopSitesGridView.this); 1.144 + } 1.145 + }); 1.146 + } 1.147 + 1.148 + @Override 1.149 + public void onDetachedFromWindow() { 1.150 + super.onDetachedFromWindow(); 1.151 + 1.152 + mUrlOpenListener = null; 1.153 + mEditPinnedSiteListener = null; 1.154 + } 1.155 + 1.156 + @Override 1.157 + protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 1.158 + mIsHandlingFocusChange = true; 1.159 + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 1.160 + mIsHandlingFocusChange = false; 1.161 + } 1.162 + 1.163 + @Override 1.164 + public void requestLayout() { 1.165 + if (!mIsHandlingFocusChange) { 1.166 + super.requestLayout(); 1.167 + } 1.168 + } 1.169 + 1.170 + /** 1.171 + * {@inheritDoc} 1.172 + */ 1.173 + @Override 1.174 + public int getColumnWidth() { 1.175 + // This method will be called from onMeasure() too. 1.176 + // It's better to use getMeasuredWidth(), as it is safe in this case. 1.177 + final int totalHorizontalSpacing = mNumColumns > 0 ? (mNumColumns - 1) * mHorizontalSpacing : 0; 1.178 + return (getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - totalHorizontalSpacing) / mNumColumns; 1.179 + } 1.180 + 1.181 + /** 1.182 + * {@inheritDoc} 1.183 + */ 1.184 + @Override 1.185 + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1.186 + // Sets the padding for this view. 1.187 + super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1.188 + 1.189 + final int measuredWidth = getMeasuredWidth(); 1.190 + if (measuredWidth == mMeasuredWidth) { 1.191 + // Return the cached values as the width is the same. 1.192 + setMeasuredDimension(mMeasuredWidth, mMeasuredHeight); 1.193 + return; 1.194 + } 1.195 + 1.196 + final int columnWidth = getColumnWidth(); 1.197 + 1.198 + // Get the first child from the adapter. 1.199 + final TopSitesGridItemView child = new TopSitesGridItemView(getContext()); 1.200 + 1.201 + // Set a default LayoutParams on the child, if it doesn't have one on its own. 1.202 + AbsListView.LayoutParams params = (AbsListView.LayoutParams) child.getLayoutParams(); 1.203 + if (params == null) { 1.204 + params = new AbsListView.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT, 1.205 + AbsListView.LayoutParams.WRAP_CONTENT); 1.206 + child.setLayoutParams(params); 1.207 + } 1.208 + 1.209 + // Measure the exact width of the child, and the height based on the width. 1.210 + // Note: the child (and TopSitesThumbnailView) takes care of calculating its height. 1.211 + int childWidthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY); 1.212 + int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 1.213 + child.measure(childWidthSpec, childHeightSpec); 1.214 + final int childHeight = child.getMeasuredHeight(); 1.215 + 1.216 + // This is the maximum width of the contents of each child in the grid. 1.217 + // Use this as the target width for thumbnails. 1.218 + final int thumbnailWidth = child.getMeasuredWidth() - child.getPaddingLeft() - child.getPaddingRight(); 1.219 + ThumbnailHelper.getInstance().setThumbnailWidth(thumbnailWidth); 1.220 + 1.221 + // Number of rows required to show these top sites. 1.222 + final int rows = (int) Math.ceil((double) mMaxSites / mNumColumns); 1.223 + final int childrenHeight = childHeight * rows; 1.224 + final int totalVerticalSpacing = rows > 0 ? (rows - 1) * mVerticalSpacing : 0; 1.225 + 1.226 + // Total height of this view. 1.227 + final int measuredHeight = childrenHeight + getPaddingTop() + getPaddingBottom() + totalVerticalSpacing; 1.228 + setMeasuredDimension(measuredWidth, measuredHeight); 1.229 + mMeasuredWidth = measuredWidth; 1.230 + mMeasuredHeight = measuredHeight; 1.231 + } 1.232 + 1.233 + @Override 1.234 + public ContextMenuInfo getContextMenuInfo() { 1.235 + return mContextMenuInfo; 1.236 + } 1.237 + 1.238 + /* 1.239 + * Update the fields of a TopSitesGridContextMenuInfo object 1.240 + * from a cursor. 1.241 + * 1.242 + * @param info context menu info object to be updated 1.243 + * @param cursor used to update the context menu info object 1.244 + */ 1.245 + private void updateContextMenuFromCursor(TopSitesGridContextMenuInfo info, Cursor cursor) { 1.246 + info.url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL)); 1.247 + info.title = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.TITLE)); 1.248 + info.isPinned = ((TopSitesCursorWrapper) cursor).isPinned(); 1.249 + } 1.250 + /** 1.251 + * Set an url open listener to be used by this view. 1.252 + * 1.253 + * @param listener An url open listener for this view. 1.254 + */ 1.255 + public void setOnUrlOpenListener(OnUrlOpenListener listener) { 1.256 + mUrlOpenListener = listener; 1.257 + } 1.258 + 1.259 + /** 1.260 + * Set an edit pinned site listener to be used by this view. 1.261 + * 1.262 + * @param listener An edit pinned site listener for this view. 1.263 + */ 1.264 + public void setOnEditPinnedSiteListener(final OnEditPinnedSiteListener listener) { 1.265 + mEditPinnedSiteListener = listener; 1.266 + } 1.267 + 1.268 + /** 1.269 + * Stores information regarding the creation of the context menu for a GridView item. 1.270 + */ 1.271 + public static class TopSitesGridContextMenuInfo extends HomeContextMenuInfo { 1.272 + public boolean isPinned = false; 1.273 + 1.274 + public TopSitesGridContextMenuInfo(View targetView, int position, long id) { 1.275 + super(targetView, position, id); 1.276 + } 1.277 + } 1.278 +}