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.toolbar;
8 import org.mozilla.gecko.GeckoAppShell;
9 import org.mozilla.gecko.GeckoEvent;
10 import org.mozilla.gecko.R;
11 import org.mozilla.gecko.gfx.BitmapUtils;
12 import org.mozilla.gecko.util.GeckoEventListener;
13 import org.mozilla.gecko.util.ThreadUtils;
14 import org.mozilla.gecko.widget.GeckoPopupMenu;
16 import org.json.JSONArray;
17 import org.json.JSONException;
18 import org.json.JSONObject;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.drawable.BitmapDrawable;
24 import android.graphics.drawable.Drawable;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.ContextMenu;
28 import android.view.Menu;
29 import android.view.MenuItem;
30 import android.view.View;
31 import android.widget.Button;
32 import android.widget.ImageButton;
33 import android.widget.ImageView;
34 import android.widget.LinearLayout;
36 import java.util.UUID;
37 import java.util.ArrayList;
39 public class PageActionLayout extends LinearLayout implements GeckoEventListener,
40 View.OnClickListener,
41 View.OnLongClickListener {
42 private final String LOGTAG = "GeckoPageActionLayout";
43 private final String MENU_BUTTON_KEY = "MENU_BUTTON_KEY";
44 private final int DEFAULT_PAGE_ACTIONS_SHOWN = 2;
46 private ArrayList<PageAction> mPageActionList;
47 private GeckoPopupMenu mPageActionsMenu;
48 private Context mContext;
49 private LinearLayout mLayout;
51 // By default it's two, can be changed by calling setNumberShown(int)
52 private int mMaxVisiblePageActions;
54 public PageActionLayout(Context context, AttributeSet attrs) {
55 super(context, attrs);
56 mContext = context;
57 mLayout = this;
59 mPageActionList = new ArrayList<PageAction>();
60 setNumberShown(DEFAULT_PAGE_ACTIONS_SHOWN);
61 refreshPageActionIcons();
63 registerEventListener("PageActions:Add");
64 registerEventListener("PageActions:Remove");
65 }
67 private void setNumberShown(int count) {
68 mMaxVisiblePageActions = count;
70 for(int index = 0; index < count; index++) {
71 if ((this.getChildCount() - 1) < index) {
72 mLayout.addView(createImageButton());
73 }
74 }
75 }
77 public void onDestroy() {
78 unregisterEventListener("PageActions:Add");
79 unregisterEventListener("PageActions:Remove");
80 }
82 protected void registerEventListener(String event) {
83 GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
84 }
86 protected void unregisterEventListener(String event) {
87 GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
88 }
90 @Override
91 public void handleMessage(String event, JSONObject message) {
92 try {
93 if (event.equals("PageActions:Add")) {
94 final String id = message.getString("id");
95 final String title = message.getString("title");
96 final String imageURL = message.optString("icon");
97 final boolean mImportant = message.getBoolean("important");
99 addPageAction(id, title, imageURL, new OnPageActionClickListeners() {
100 @Override
101 public void onClick(String id) {
102 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:Clicked", id));
103 }
105 @Override
106 public boolean onLongClick(String id) {
107 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:LongClicked", id));
108 return true;
109 }
110 }, mImportant);
111 } else if (event.equals("PageActions:Remove")) {
112 final String id = message.getString("id");
114 removePageAction(id);
115 }
116 } catch(JSONException ex) {
117 Log.e(LOGTAG, "Error deocding", ex);
118 }
119 }
121 private void addPageAction(final String id, final String title, final String imageData, final OnPageActionClickListeners mOnPageActionClickListeners, boolean mImportant) {
122 final PageAction pageAction = new PageAction(id, title, null, mOnPageActionClickListeners, mImportant);
124 int insertAt = mPageActionList.size();
125 while(insertAt > 0 && mPageActionList.get(insertAt-1).isImportant()) {
126 insertAt--;
127 }
128 mPageActionList.add(insertAt, pageAction);
130 BitmapUtils.getDrawable(mContext, imageData, new BitmapUtils.BitmapLoader() {
131 @Override
132 public void onBitmapFound(final Drawable d) {
133 if (mPageActionList.contains(pageAction)) {
134 pageAction.setDrawable(d);
135 refreshPageActionIcons();
136 }
137 }
138 });
139 }
141 private void removePageAction(String id) {
142 for(int i = 0; i < mPageActionList.size(); i++) {
143 if (mPageActionList.get(i).getID().equals(id)) {
144 mPageActionList.remove(i);
145 refreshPageActionIcons();
146 return;
147 }
148 }
149 }
151 private ImageButton createImageButton() {
152 ImageButton imageButton = new ImageButton(mContext, null, R.style.UrlBar_ImageButton_Icon);
153 imageButton.setLayoutParams(new LayoutParams(mContext.getResources().getDimensionPixelSize(R.dimen.page_action_button_width), LayoutParams.MATCH_PARENT));
154 imageButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
155 imageButton.setOnClickListener(this);
156 imageButton.setOnLongClickListener(this);
157 return imageButton;
158 }
160 @Override
161 public void onClick(View v) {
162 String buttonClickedId = (String)v.getTag();
163 if (buttonClickedId != null) {
164 if (buttonClickedId.equals(MENU_BUTTON_KEY)) {
165 showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1);
166 } else {
167 getPageActionWithId(buttonClickedId).onClick();
168 }
169 }
170 }
172 @Override
173 public boolean onLongClick(View v) {
174 String buttonClickedId = (String)v.getTag();
175 if (buttonClickedId.equals(MENU_BUTTON_KEY)) {
176 showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1);
177 return true;
178 } else {
179 return getPageActionWithId(buttonClickedId).onLongClick();
180 }
181 }
183 private void setActionForView(final ImageButton view, final PageAction pageAction) {
184 if (pageAction == null) {
185 view.setTag(null);
186 ThreadUtils.postToUiThread(new Runnable() {
187 @Override
188 public void run () {
189 view.setImageDrawable(null);
190 view.setVisibility(View.GONE);
191 view.setContentDescription(null);
192 }
193 });
194 return;
195 }
197 view.setTag(pageAction.getID());
198 ThreadUtils.postToUiThread(new Runnable() {
199 @Override
200 public void run () {
201 view.setImageDrawable(pageAction.getDrawable());
202 view.setVisibility(View.VISIBLE);
203 view.setContentDescription(pageAction.getTitle());
204 }
205 });
206 }
208 private void refreshPageActionIcons() {
209 final Resources resources = mContext.getResources();
210 for(int index = 0; index < this.getChildCount(); index++) {
211 final ImageButton v = (ImageButton)this.getChildAt(index);
212 final PageAction pageAction = getPageActionForViewAt(index);
214 // If there are more pageactions then buttons, set the menu icon. Otherwise set the page action's icon if there is a page action.
215 if (index == (this.getChildCount() - 1) && mPageActionList.size() > mMaxVisiblePageActions) {
216 v.setTag(MENU_BUTTON_KEY);
217 ThreadUtils.postToUiThread(new Runnable() {
218 @Override
219 public void run () {
220 v.setImageDrawable(resources.getDrawable(R.drawable.icon_pageaction));
221 v.setVisibility((pageAction != null) ? View.VISIBLE : View.GONE);
222 v.setContentDescription(resources.getString(R.string.page_action_dropmarker_description));
223 }
224 });
225 } else {
226 setActionForView(v, pageAction);
227 }
228 }
229 }
231 private PageAction getPageActionForViewAt(int index) {
232 /**
233 * We show the user the most recent pageaction added since this keeps the user aware of any new page actions being added
234 * Also, the order of the pageAction is important i.e. if a page action is added, instead of shifting the pagactions to the
235 * left to make space for the new one, it would be more visually appealing to have the pageaction appear in the blank space.
236 *
237 * buttonIndex is needed for this reason because every new View added to PageActionLayout gets added to the right of its neighbouring View.
238 * Hence the button on the very leftmost has the index 0. We want our pageactions to start from the rightmost
239 * and hence we maintain the insertion order of the child Views which is essentially the reverse of their index
240 */
242 int buttonIndex = (this.getChildCount() - 1) - index;
243 int totalVisibleButtons = ((mPageActionList.size() < this.getChildCount()) ? mPageActionList.size() : this.getChildCount());
245 if (mPageActionList.size() > buttonIndex) {
246 // Return the pageactions starting from the end of the list for the number of visible pageactions.
247 return mPageActionList.get((mPageActionList.size() - totalVisibleButtons) + buttonIndex);
248 }
249 return null;
250 }
252 private PageAction getPageActionWithId(String id) {
253 for(int i = 0; i < mPageActionList.size(); i++) {
254 PageAction pageAction = mPageActionList.get(i);
255 if (pageAction.getID().equals(id)) {
256 return pageAction;
257 }
258 }
259 return null;
260 }
262 private void showMenu(View mPageActionButton, int toShow) {
263 if (mPageActionsMenu == null) {
264 mPageActionsMenu = new GeckoPopupMenu(mPageActionButton.getContext(), mPageActionButton);
265 mPageActionsMenu.inflate(0);
266 mPageActionsMenu.setOnMenuItemClickListener(new GeckoPopupMenu.OnMenuItemClickListener() {
267 @Override
268 public boolean onMenuItemClick(MenuItem item) {
269 int id = item.getItemId();
270 for(int i = 0; i < mPageActionList.size(); i++) {
271 PageAction pageAction = mPageActionList.get(i);
272 if (pageAction.key() == id) {
273 pageAction.onClick();
274 return true;
275 }
276 }
277 return false;
278 }
279 });
280 }
281 Menu menu = mPageActionsMenu.getMenu();
282 menu.clear();
284 for(int i = 0; i < mPageActionList.size(); i++) {
285 if (i < toShow) {
286 PageAction pageAction = mPageActionList.get(i);
287 MenuItem item = menu.add(Menu.NONE, pageAction.key(), Menu.NONE, pageAction.getTitle());
288 item.setIcon(pageAction.getDrawable());
289 }
290 }
291 mPageActionsMenu.show();
292 }
294 private static interface OnPageActionClickListeners {
295 public void onClick(String id);
296 public boolean onLongClick(String id);
297 }
299 private static class PageAction {
300 private OnPageActionClickListeners mOnPageActionClickListeners;
301 private Drawable mDrawable;
302 private String mTitle;
303 private String mId;
304 private int key;
305 private boolean mImportant;
307 public PageAction(String id,
308 String title,
309 Drawable image,
310 OnPageActionClickListeners onPageActionClickListeners,
311 boolean important) {
312 mId = id;
313 mTitle = title;
314 mDrawable = image;
315 mOnPageActionClickListeners = onPageActionClickListeners;
316 mImportant = important;
318 key = UUID.fromString(mId.subSequence(1, mId.length() - 2).toString()).hashCode();
319 }
321 public Drawable getDrawable() {
322 return mDrawable;
323 }
325 public void setDrawable(Drawable d) {
326 mDrawable = d;
327 }
329 public String getTitle() {
330 return mTitle;
331 }
333 public String getID() {
334 return mId;
335 }
337 public int key() {
338 return key;
339 }
341 public boolean isImportant() {
342 return mImportant;
343 }
345 public void onClick() {
346 if (mOnPageActionClickListeners != null) {
347 mOnPageActionClickListeners.onClick(mId);
348 }
349 }
351 public boolean onLongClick() {
352 if (mOnPageActionClickListeners != null) {
353 return mOnPageActionClickListeners.onLongClick(mId);
354 }
355 return false;
356 }
357 }
358 }