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