1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 package org.mozilla.gecko.tabs; 6 7 import org.mozilla.gecko.R; 8 import org.mozilla.gecko.Tab; 9 import org.mozilla.gecko.Tabs; 10 import org.mozilla.gecko.icons.IconResponse; 11 import org.mozilla.gecko.icons.Icons; 12 import org.mozilla.gecko.widget.FaviconView; 13 import org.mozilla.gecko.widget.HoverDelegateWithReset; 14 import org.mozilla.gecko.widget.TabThumbnailWrapper; 15 import org.mozilla.gecko.widget.TouchDelegateWithReset; 16 import org.mozilla.gecko.widget.themed.ThemedRelativeLayout; 17 18 import android.content.Context; 19 import android.content.res.Resources; 20 import android.graphics.Rect; 21 import android.graphics.drawable.Drawable; 22 import android.support.v7.widget.ViewUtils; 23 import android.text.TextUtils; 24 import android.util.AttributeSet; 25 import android.util.TypedValue; 26 import android.view.MotionEvent; 27 import android.view.View; 28 import android.widget.Checkable; 29 import android.widget.ImageView; 30 import android.widget.LinearLayout; 31 import android.widget.TextView; 32 33 import java.util.concurrent.Future; 34 35 public class TabsLayoutItemView extends LinearLayout 36 implements Checkable { 37 private static final String LOGTAG = "Gecko" + TabsLayoutItemView.class.getSimpleName(); 38 private static final int[] STATE_CHECKED = { android.R.attr.state_checked }; 39 private boolean mChecked; 40 41 private int mTabId; 42 private TextView mTitle; 43 private TabsPanelThumbnailView mThumbnail; 44 private ImageView mCloseButton; 45 private TabThumbnailWrapper mThumbnailWrapper; 46 private HoverDelegateWithReset mHoverDelegate; 47 48 private FaviconView mFaviconView; 49 private Future<IconResponse> mOngoingIconLoad; 50 TabsLayoutItemView(Context context, AttributeSet attrs)51 public TabsLayoutItemView(Context context, AttributeSet attrs) { 52 super(context, attrs); 53 } 54 55 @Override onCreateDrawableState(int extraSpace)56 public int[] onCreateDrawableState(int extraSpace) { 57 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 58 59 if (mChecked) { 60 mergeDrawableStates(drawableState, STATE_CHECKED); 61 } 62 63 return drawableState; 64 } 65 66 @Override isEnabled()67 public boolean isEnabled() { 68 return true; 69 } 70 71 @Override isChecked()72 public boolean isChecked() { 73 return mChecked; 74 } 75 76 @Override setChecked(boolean checked)77 public void setChecked(boolean checked) { 78 if (mChecked == checked) { 79 return; 80 } 81 82 mChecked = checked; 83 refreshDrawableState(); 84 85 int count = getChildCount(); 86 for (int i = 0; i < count; i++) { 87 final View child = getChildAt(i); 88 if (child instanceof Checkable) { 89 ((Checkable) child).setChecked(checked); 90 } 91 } 92 } 93 94 @Override toggle()95 public void toggle() { 96 mChecked = !mChecked; 97 } 98 setCloseOnClickListener(OnClickListener mOnClickListener)99 public void setCloseOnClickListener(OnClickListener mOnClickListener) { 100 mCloseButton.setOnClickListener(mOnClickListener); 101 } 102 103 @Override onFinishInflate()104 protected void onFinishInflate() { 105 super.onFinishInflate(); 106 mTitle = (TextView) findViewById(R.id.title); 107 mThumbnail = (TabsPanelThumbnailView) findViewById(R.id.thumbnail); 108 mCloseButton = (ImageView) findViewById(R.id.close); 109 mThumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper); 110 mFaviconView = (FaviconView) findViewById(R.id.favicon); 111 112 growCloseButtonHitArea(); 113 } 114 growCloseButtonHitArea()115 private void growCloseButtonHitArea() { 116 addOnLayoutChangeListener(new OnLayoutChangeListener() { 117 @Override 118 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 119 // Ideally we want the close button hit area to be 40x40dp but we are constrained by the height of the parent, so 120 // we make it as tall as the parent view and 40dp across. 121 final int targetHitArea = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics()); 122 123 final Rect hitRect = getHitRectRelatively(targetHitArea); 124 125 setTouchDelegate(new TouchDelegateWithReset(hitRect, mCloseButton)); 126 setHoverDelegate(new HoverDelegateWithReset(hitRect, mCloseButton)); 127 } 128 }); 129 } 130 getHitRectRelatively(int targetHitArea)131 private Rect getHitRectRelatively(int targetHitArea) { 132 final boolean isRtl = ViewUtils.isLayoutRtl(this); 133 final Rect hitRect = new Rect(); 134 hitRect.top = 0; 135 hitRect.right = isRtl ? targetHitArea : getWidth(); 136 hitRect.left = isRtl ? 0 : getWidth() - targetHitArea; 137 hitRect.bottom = targetHitArea; 138 return hitRect; 139 } 140 141 /** 142 * Sets the HoverDelegate for this View. 143 */ setHoverDelegate(HoverDelegateWithReset delegate)144 public void setHoverDelegate(HoverDelegateWithReset delegate) { 145 mHoverDelegate = delegate; 146 } 147 148 @Override onHoverEvent(MotionEvent event)149 public boolean onHoverEvent(MotionEvent event) { 150 if (mHoverDelegate != null) { 151 if (mHoverDelegate.onHoverEvent(event)) { 152 return true; 153 } 154 } 155 156 return super.onHoverEvent(event); 157 } 158 assignValues(Tab tab)159 protected void assignValues(Tab tab) { 160 if (tab == null) { 161 return; 162 } 163 164 mTabId = tab.getId(); 165 166 setChecked(Tabs.getInstance().isSelectedTab(tab)); 167 168 Drawable thumbnailImage = tab.getThumbnail(); 169 mThumbnail.setImageDrawable(thumbnailImage); 170 171 mThumbnail.setPrivateMode(tab.isPrivate()); 172 173 if (mThumbnailWrapper != null) { 174 mThumbnailWrapper.setRecording(tab.isRecording()); 175 } 176 177 final String tabTitle = tab.getDisplayTitle(); 178 mTitle.setText(tabTitle); 179 mCloseButton.setTag(this); 180 181 if (tab.isAudioPlaying()) { 182 mFaviconView.setImageResource(R.drawable.tab_audio_playing); 183 final String tabTitleWithAudio = 184 getResources().getString(R.string.tab_title_prefix_is_playing_audio, tabTitle); 185 mTitle.setContentDescription(tabTitleWithAudio); 186 } else { 187 final String url = tab.getURL(); 188 if (TextUtils.isEmpty(url)) { 189 // Ignore loading favicon without url. 190 return; 191 } 192 193 if (mOngoingIconLoad != null) { 194 mOngoingIconLoad.cancel(true); 195 } 196 197 final Resources resources = getResources(); 198 final int iconSize = resources.getDimensionPixelSize(R.dimen.tab_favicon_size); 199 final float textSize = resources.getDimensionPixelSize(R.dimen.tab_favicon_text_size); 200 201 final Context appContext = getContext().getApplicationContext(); 202 mOngoingIconLoad = Icons.with(appContext) 203 .pageUrl(url) 204 .skipNetwork() 205 .targetSize(iconSize) 206 .textSize(textSize) 207 .build() 208 .execute(mFaviconView.createIconCallback()); 209 210 mTitle.setContentDescription(tabTitle); 211 } 212 } 213 getTabId()214 public int getTabId() { 215 return mTabId; 216 } 217 setThumbnail(Drawable thumbnail)218 public void setThumbnail(Drawable thumbnail) { 219 mThumbnail.setImageDrawable(thumbnail); 220 } 221 setCloseVisible(boolean visible)222 public void setCloseVisible(boolean visible) { 223 mCloseButton.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 224 } 225 setPrivateMode(boolean isPrivate)226 public void setPrivateMode(boolean isPrivate) { 227 ((ThemedRelativeLayout) findViewById(R.id.wrapper)).setPrivateMode(isPrivate); 228 } 229 } 230