Bug 734877: Implement a page actions api. r=wesj

This commit is contained in:
Shilpan Bhagat 2013-07-15 17:19:05 -07:00
parent f2132c1a35
commit a46d91589e
14 changed files with 506 additions and 71 deletions

View File

@ -11,14 +11,14 @@ import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.menu.GeckoMenu;
import org.mozilla.gecko.menu.MenuPopup;
import org.mozilla.gecko.PageActionLayout;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.StringUtils;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UiAsyncTask;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.util.GeckoEventListener;
import org.json.JSONObject;
@ -63,14 +63,14 @@ import android.widget.RelativeLayout;
import android.widget.RelativeLayout.LayoutParams;
import android.widget.ViewSwitcher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class BrowserToolbar extends GeckoRelativeLayout
implements Tabs.OnTabsChangedListener,
GeckoMenu.ActionItemBarPresenter,
Animation.AnimationListener {
Animation.AnimationListener,
GeckoEventListener {
private static final String LOGTAG = "GeckoToolbar";
public static final String PREF_TITLEBAR_MODE = "browser.chrome.titlebarMode";
private LayoutParams mAwesomeBarParams;
@ -88,7 +88,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
public ImageButton mFavicon;
public ImageButton mStop;
public ImageButton mSiteSecurity;
public ImageButton mReader;
public PageActionLayout mPageActionLayout;
private AnimationDrawable mProgressSpinner;
private TabCounter mTabsCounter;
private ImageView mShadow;
@ -183,6 +183,9 @@ public class BrowserToolbar extends GeckoRelativeLayout
mDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext));
mPrivateDomainColor = new ForegroundColorSpan(res.getColor(R.color.url_bar_domaintext_private));
registerEventListener("Reader:Click");
registerEventListener("Reader:LongClick");
mShowSiteSecurity = false;
mShowReader = false;
@ -221,8 +224,8 @@ public class BrowserToolbar extends GeckoRelativeLayout
mProgressSpinner = (AnimationDrawable) res.getDrawable(R.drawable.progress_spinner);
mStop = (ImageButton) findViewById(R.id.stop);
mReader = (ImageButton) findViewById(R.id.reader);
mShadow = (ImageView) findViewById(R.id.shadow);
mPageActionLayout = (PageActionLayout) findViewById(R.id.page_action_layout);
if (Build.VERSION.SDK_INT >= 16) {
mShadow.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
@ -237,9 +240,9 @@ public class BrowserToolbar extends GeckoRelativeLayout
// order appropriately.
if (HardwareUtils.isTablet()) {
mFocusOrder = Arrays.asList(mTabs, mBack, mForward, this,
mSiteSecurity, mReader, mStop, mActionItemBar, mMenu);
mSiteSecurity, mPageActionLayout, mStop, mActionItemBar, mMenu);
} else {
mFocusOrder = Arrays.asList(this, mSiteSecurity, mReader, mStop,
mFocusOrder = Arrays.asList(this, mSiteSecurity, mPageActionLayout, mStop,
mTabs, mMenu);
}
}
@ -353,28 +356,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
}
});
mReader.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null) {
tab.toggleReaderMode();
}
}
});
mReader.setOnLongClickListener(new Button.OnLongClickListener() {
public boolean onLongClick(View v) {
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null) {
tab.addToReadingList();
return true;
}
return false;
}
});
mShadow.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
@ -474,7 +455,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
if (showProgress && tab.getState() == Tab.STATE_LOADING)
setProgressVisibility(true);
setSecurityMode(tab.getSecurityMode());
setReaderMode(tab.getReaderEnabled());
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
}
break;
case STOP:
@ -519,7 +500,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
break;
case READER_ENABLED:
if (Tabs.getInstance().isSelectedTab(tab)) {
setReaderMode(tab.getReaderEnabled());
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
}
break;
}
@ -537,7 +518,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
mFavicon.setNextFocusDownId(nextId);
mStop.setNextFocusDownId(nextId);
mSiteSecurity.setNextFocusDownId(nextId);
mReader.setNextFocusDownId(nextId);
mPageActionLayout.setNextFocusDownId(nextId);
mMenu.setNextFocusDownId(nextId);
}
@ -613,7 +594,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
ViewHelper.setTranslationX(mMenuIcon, curveTranslation);
}
ViewHelper.setAlpha(mReader, 0);
ViewHelper.setAlpha(mPageActionLayout, 0);
ViewHelper.setAlpha(mStop, 0);
}
@ -661,7 +642,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
// Fade toolbar buttons (reader, stop) after the entry
// is schrunk back to its original size.
buttonsAnimator.attach(mReader,
buttonsAnimator.attach(mPageActionLayout,
PropertyAnimator.Property.ALPHA,
1);
buttonsAnimator.attach(mStop,
@ -708,7 +689,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
setSelected(true);
// Hide stop/reader buttons immediately
ViewHelper.setAlpha(mReader, 0);
ViewHelper.setAlpha(mPageActionLayout, 0);
ViewHelper.setAlpha(mStop, 0);
// Slide the right side elements of the toolbar
@ -827,22 +808,16 @@ public class BrowserToolbar extends GeckoRelativeLayout
// Handle the viewing mode page actions
setSiteSecurityVisibility(mShowSiteSecurity && !isLoading);
// Handle the readerMode image and visibility: We show the reader mode button if 1) you can
// enter reader mode for current page or 2) if you're already in reader mode,
// in which case we show the reader mode "close" (reader_active) icon.
boolean inReaderMode = false;
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null)
inReaderMode = ReaderModeUtils.isAboutReader(tab.getURL());
mReader.setImageResource(inReaderMode ? R.drawable.reader_active : R.drawable.reader);
mReader.setVisibility(!isLoading && (mShowReader || inReaderMode) ? View.VISIBLE : View.GONE);
mPageActionLayout.setVisibility(!isLoading ? View.VISIBLE : View.GONE);
// We want title to fill the whole space available for it when there are icons
// being shown on the right side of the toolbar as the icons already have some
// padding in them. This is just to avoid wasting space when icons are shown.
mTitle.setPadding(0, 0, (!isLoading && !(mShowReader || inReaderMode) ? mTitlePadding : 0), 0);
updateFocusOrder();
}
@ -926,7 +901,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
String url = tab.getURL();
// Only set shadow to visible when not on about screens except about:blank.
visible &= !(url == null || (url.startsWith("about:") &&
visible &= !(url == null || (url.startsWith("about:") &&
!url.equals("about:blank")));
if ((mShadow.getVisibility() == View.VISIBLE) != visible) {
@ -997,11 +972,6 @@ public class BrowserToolbar extends GeckoRelativeLayout
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
}
private void setReaderMode(boolean showReader) {
mShowReader = showReader;
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
}
public void prepareTabsAnimation(PropertyAnimator animator, boolean tabsAreShown) {
if (!tabsAreShown) {
PropertyAnimator buttonsAnimator =
@ -1162,7 +1132,7 @@ public class BrowserToolbar extends GeckoRelativeLayout
setFavicon(tab.getFavicon());
setProgressVisibility(tab.getState() == Tab.STATE_LOADING);
setSecurityMode(tab.getSecurityMode());
setReaderMode(tab.getReaderEnabled());
setPageActionVisibility(mStop.getVisibility() == View.VISIBLE);
setShadowVisibility(true);
updateBackButton(tab.canDoBack());
updateForwardButton(tab.canDoForward());
@ -1189,6 +1159,9 @@ public class BrowserToolbar extends GeckoRelativeLayout
mPrefObserverId = null;
}
Tabs.unregisterOnTabsChangedListener(this);
unregisterEventListener("Reader:Click");
unregisterEventListener("Reader:LongClick");
}
public boolean openOptionsMenu() {
@ -1225,4 +1198,27 @@ public class BrowserToolbar extends GeckoRelativeLayout
return true;
}
protected void registerEventListener(String event) {
GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
}
protected void unregisterEventListener(String event) {
GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
}
@Override
public void handleMessage(String event, JSONObject message) {
if (event.equals("Reader:Click")) {
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null) {
tab.toggleReaderMode();
}
} else if (event.equals("Reader:LongClick")) {
Tab tab = Tabs.getInstance().getSelectedTab();
if (tab != null) {
tab.addToReadingList();
}
}
}
}

View File

@ -129,6 +129,7 @@ FENNEC_JAVA_FILES = \
NotificationService.java \
NSSBridge.java \
OrderedBroadcastHelper.java \
PageActionLayout.java \
PrefsHelper.java \
PrivateDataPreference.java \
PrivateTab.java \
@ -641,6 +642,7 @@ RES_DRAWABLE_MDPI = \
res/drawable-mdpi/ic_menu_new_tab.png \
res/drawable-mdpi/ic_menu_reload.png \
res/drawable-mdpi/ic_status_logo.png \
res/drawable-mdpi/icon_pageaction.png \
res/drawable-mdpi/progress_spinner_1.png \
res/drawable-mdpi/progress_spinner_2.png \
res/drawable-mdpi/progress_spinner_3.png \
@ -763,6 +765,7 @@ RES_DRAWABLE_HDPI = \
res/drawable-hdpi/ic_menu_new_tab.png \
res/drawable-hdpi/ic_menu_reload.png \
res/drawable-hdpi/ic_status_logo.png \
res/drawable-hdpi/icon_pageaction.png \
res/drawable-hdpi/tab_indicator_divider.9.png \
res/drawable-hdpi/tab_indicator_selected.9.png \
res/drawable-hdpi/tab_indicator_selected_focused.9.png \
@ -861,6 +864,7 @@ RES_DRAWABLE_XHDPI = \
res/drawable-xhdpi/ic_menu_new_tab.png \
res/drawable-xhdpi/ic_menu_reload.png \
res/drawable-xhdpi/ic_status_logo.png \
res/drawable-xhdpi/icon_pageaction.png \
res/drawable-xhdpi/spinner_default.9.png \
res/drawable-xhdpi/spinner_focused.9.png \
res/drawable-xhdpi/spinner_pressed.9.png \

View File

@ -0,0 +1,315 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.GeckoPopupMenu;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import java.util.UUID;
import java.util.LinkedHashMap;
public class PageActionLayout extends LinearLayout implements GeckoEventListener,
View.OnClickListener,
View.OnLongClickListener {
private final String LOGTAG = "GeckoPageActionLayout";
private final String MENU_BUTTON_KEY = "MENU_BUTTON_KEY";
private final int DEFAULT_PAGE_ACTIONS_SHOWN = 2;
private LinkedHashMap<String, PageAction> mPageActionList;
private GeckoPopupMenu mPageActionsMenu;
private Context mContext;
private LinearLayout mLayout;
// By default it's two, can be changed by calling setNumberShown(int)
private int mMaxVisiblePageActions;
public PageActionLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mLayout = this;
mPageActionList = new LinkedHashMap<String, PageAction>();
setNumberShown(DEFAULT_PAGE_ACTIONS_SHOWN);
registerEventListener("PageActions:Add");
registerEventListener("PageActions:Remove");
}
public void setNumberShown(int count) {
mMaxVisiblePageActions = count;
for(int index = 0; index < count; index++) {
if ((this.getChildCount() - 1) < index) {
mLayout.addView(createImageButton());
}
}
}
public void onDestroy() {
unregisterEventListener("PageActions:Add");
unregisterEventListener("PageActions:Remove");
}
protected void registerEventListener(String event) {
GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
}
protected void unregisterEventListener(String event) {
GeckoAppShell.getEventDispatcher().unregisterEventListener(event, this);
}
@Override
public void handleMessage(String event, JSONObject message) {
try {
if (event.equals("PageActions:Add")) {
final String id = message.getString("id");
final String title = message.getString("title");
final String imageURL = message.optString("icon");
addPageAction(id, title, imageURL, new OnPageActionClickListeners() {
@Override
public void onClick(String id) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:Clicked", id));
}
@Override
public boolean onLongClick(String id) {
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("PageActions:LongClicked", id));
return true;
}
});
} else if (event.equals("PageActions:Remove")) {
final String id = message.getString("id");
removePageAction(id);
}
} catch(JSONException ex) {
Log.e(LOGTAG, "Error deocding", ex);
}
}
public void addPageAction(final String id, final String title, final String imageData, final OnPageActionClickListeners mOnPageActionClickListeners) {
final PageAction pageAction = new PageAction(id, title, null, mOnPageActionClickListeners);
mPageActionList.put(id, pageAction);
BitmapUtils.getDrawable(mContext, imageData, new BitmapUtils.BitmapLoader() {
@Override
public void onBitmapFound(final Drawable d) {
if (mPageActionList.containsKey(id)) {
pageAction.setDrawable(d);
refreshPageActionIcons();
}
}
});
}
public void removePageAction(String id) {
mPageActionList.remove(id);
refreshPageActionIcons();
}
private ImageButton createImageButton() {
ImageButton imageButton = new ImageButton(mContext, null, R.style.AddressBar_ImageButton_Icon);
imageButton.setLayoutParams(new LayoutParams(mContext.getResources().getDimensionPixelSize(R.dimen.page_action_button_width), LayoutParams.MATCH_PARENT));
imageButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
imageButton.setOnClickListener(this);
imageButton.setOnLongClickListener(this);
return imageButton;
}
@Override
public void onClick(View v) {
String buttonClickedId = (String)v.getTag();
if (buttonClickedId != null) {
if (buttonClickedId.equals(MENU_BUTTON_KEY)) {
showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1);
} else {
mPageActionList.get(buttonClickedId).onClick();
}
}
}
@Override
public boolean onLongClick(View v) {
String buttonClickedId = (String)v.getTag();
if (buttonClickedId.equals(MENU_BUTTON_KEY)) {
showMenu(v, mPageActionList.size() - mMaxVisiblePageActions + 1);
return true;
} else {
return mPageActionList.get(buttonClickedId).onLongClick();
}
}
private void refreshPageActionIcons() {
final Resources resources = mContext.getResources();
for(int index = 0; index < this.getChildCount(); index++) {
final ImageButton v = (ImageButton)this.getChildAt(index);
final PageAction pageAction = getPageActionForViewAt(index);
if (index == (this.getChildCount() - 1)) {
String id = (pageAction != null) ? pageAction.getID() : null;
v.setTag((mPageActionList.size() > mMaxVisiblePageActions) ? MENU_BUTTON_KEY : id);
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run () {
// If there are more pageactions then buttons, set the menu icon. Otherwise set the page action's icon if there is a page action.
Drawable d = (pageAction != null) ? pageAction.getDrawable() : null;
v.setImageDrawable((mPageActionList.size() > mMaxVisiblePageActions) ? resources.getDrawable(R.drawable.icon_pageaction) : d);
v.setVisibility((pageAction != null) ? View.VISIBLE : View.GONE);
}
});
} else {
v.setTag((pageAction != null) ? pageAction.getID() : null);
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run () {
v.setImageDrawable((pageAction != null) ? pageAction.getDrawable() : null);
v.setVisibility((pageAction != null) ? View.VISIBLE : View.GONE);
}
});
}
}
}
private PageAction getPageActionForViewAt(int index) {
/**
* We show the user the most recent pageaction added since this keeps the user aware of any new page actions being added
* Also, the order of the pageAction is important i.e. if a page action is added, instead of shifting the pagactions to the
* left to make space for the new one, it would be more visually appealing to have the pageaction appear in the blank space.
*
* buttonIndex is needed for this reason because every new View added to PageActionLayout gets added to the right of its neighbouring View.
* Hence the button on the very leftmost has the index 0. We want our pageactions to start from the rightmost
* and hence we maintain the insertion order of the child Views which is essentially the reverse of their index
*/
int buttonIndex = (this.getChildCount() - 1) - index;
int totalVisibleButtons = ((mPageActionList.size() < this.getChildCount()) ? mPageActionList.size() : this.getChildCount());
if (mPageActionList.size() > buttonIndex) {
// Return the pageactions starting from the end of the list for the number of visible pageactions.
return getPageActionAt((mPageActionList.size() - totalVisibleButtons) + buttonIndex);
}
return null;
}
private PageAction getPageActionAt(int index) {
int count = 0;
for(PageAction pageAction : mPageActionList.values()) {
if (count == index) {
return pageAction;
}
count++;
}
return null;
}
private void showMenu(View mPageActionButton, int toShow) {
if (mPageActionsMenu == null) {
mPageActionsMenu = new GeckoPopupMenu(mPageActionButton.getContext(), mPageActionButton);
mPageActionsMenu.inflate(0);
mPageActionsMenu.setOnMenuItemClickListener(new GeckoPopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
for(PageAction pageAction : mPageActionList.values()) {
if (pageAction.key() == item.getItemId()) {
pageAction.onClick();
}
}
return true;
}
});
}
Menu menu = mPageActionsMenu.getMenu();
menu.clear();
int count = 0;
for(PageAction pageAction : mPageActionList.values()) {
if (count < toShow) {
MenuItem item = menu.add(Menu.NONE, pageAction.key(), Menu.NONE, pageAction.getTitle());
item.setIcon(pageAction.getDrawable());
}
count++;
}
mPageActionsMenu.show();
}
public static interface OnPageActionClickListeners {
public void onClick(String id);
public boolean onLongClick(String id);
}
private static class PageAction {
private OnPageActionClickListeners mOnPageActionClickListeners;
private Drawable mDrawable;
private String mTitle;
private String mId;
private int key;
public PageAction(String id, String title, Drawable image, OnPageActionClickListeners mOnPageActionClickListeners) {
this.mId = id;
this.mTitle = title;
this.mDrawable = image;
this.mOnPageActionClickListeners = mOnPageActionClickListeners;
this.key = UUID.fromString(mId.subSequence(1, mId.length() - 2).toString()).hashCode();
}
public Drawable getDrawable() {
return mDrawable;
}
public void setDrawable(Drawable d) {
this.mDrawable = d;
}
public String getTitle() {
return mTitle;
}
public String getID() {
return mId;
}
public int key() {
return key;
}
public void onClick() {
if (mOnPageActionClickListeners != null) {
mOnPageActionClickListeners.onClick(mId);
}
}
public boolean onLongClick() {
if (mOnPageActionClickListeners != null) {
return mOnPageActionClickListeners.onLongClick(mId);
}
return false;
}
}
}

View File

@ -98,14 +98,14 @@ public class MenuPopup extends PopupWindow {
int screenWidth = mResources.getDisplayMetrics().widthPixels;
int arrowWidth = mResources.getDimensionPixelSize(R.dimen.menu_popup_arrow_width);
int arrowOffset = (anchor.getWidth() - arrowWidth)/2;
if (anchorLocation[0] + mPopupWidth <= screenWidth) {
// left align
((LayoutParams) mArrowTop.getLayoutParams()).rightMargin = mPopupWidth - anchor.getWidth() + arrowOffset;
((LayoutParams) mArrowBottom.getLayoutParams()).rightMargin = mPopupWidth - anchor.getWidth() + arrowOffset;
} else {
// right align
((LayoutParams) mArrowTop.getLayoutParams()).rightMargin = mArrowMargin;
((LayoutParams) mArrowTop.getLayoutParams()).rightMargin = screenWidth - anchorLocation[0] - anchor.getWidth()/2 - arrowWidth/2;
((LayoutParams) mArrowBottom.getLayoutParams()).rightMargin = mArrowMargin;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

View File

@ -103,11 +103,12 @@
android:layout_gravity="center_vertical"
gecko:autoUpdateTheme="false"/>
<ImageButton android:id="@+id/reader"
style="@style/AddressBar.ImageButton.Icon"
android:src="@drawable/reader"
android:contentDescription="@string/reader"
android:visibility="gone"/>
<org.mozilla.gecko.PageActionLayout android:id="@+id/page_action_layout"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="@dimen/browser_toolbar_button_padding"
android:visibility="gone"
android:orientation="horizontal"/>
<ImageButton android:id="@+id/stop"
style="@style/AddressBar.ImageButton.Icon"

View File

@ -129,11 +129,12 @@
android:layout_gravity="center_vertical"
gecko:autoUpdateTheme="false"/>
<ImageButton android:id="@+id/reader"
style="@style/AddressBar.ImageButton.Icon"
android:src="@drawable/reader"
android:contentDescription="@string/reader"
android:visibility="gone"/>
<org.mozilla.gecko.PageActionLayout android:id="@+id/page_action_layout"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="12dp"
android:visibility="gone"
android:orientation="horizontal"/>
<ImageButton android:id="@+id/stop"
style="@style/AddressBar.ImageButton.Icon"

View File

@ -76,5 +76,8 @@
<dimen name="validation_message_margin_top">6dp</dimen>
<dimen name="forward_default_offset">-13dip</dimen>
<dimen name="addressbar_offset_left">32dp</dimen>
<!-- PageActionButtons dimensions -->
<dimen name="page_action_button_width">32dp</dimen>
<dimen name="toast_button_padding">8dp</dimen>
</resources>

View File

@ -193,7 +193,6 @@
<string name="reading_list_removed">&reading_list_removed;</string>
<string name="reading_list_failed">&reading_list_failed;</string>
<string name="reading_list_duplicate">&reading_list_duplicate;</string>
<string name="reader">&reader;</string>
<string name="contextmenu_open_new_tab">&contextmenu_open_new_tab;</string>
<string name="contextmenu_open_private_tab">&contextmenu_open_private_tab;</string>

View File

@ -17,10 +17,10 @@ import android.view.MenuItem;
import android.view.View;
/**
* A PopupMenu that uses the custom GeckoMenu. This menu is
* A PopupMenu that uses the custom GeckoMenu. This menu is
* usually tied to an anchor, and show as a dropdrown from the anchor.
*/
public class GeckoPopupMenu implements GeckoMenu.Callback,
public class GeckoPopupMenu implements GeckoMenu.Callback,
GeckoMenu.MenuPresenter {
// An interface for listeners for dismissal.
@ -92,8 +92,9 @@ public class GeckoPopupMenu implements GeckoMenu.Callback,
* @param menuRes The menu resource to be inflated.
*/
public void inflate(int menuRes) {
mMenuInflater.inflate(menuRes, mMenu);
if (menuRes > 0) {
mMenuInflater.inflate(menuRes, mMenu);
}
mMenuPanel.addView(mMenu);
mMenuPopup.setPanelView(mMenuPanel);
}

View File

@ -731,7 +731,7 @@ var BrowserApp = {
if (tabs[i].browser.contentWindow == aWindow)
return tabs[i];
}
return null;
return null;
},
getBrowserForWindow: function getBrowserForWindow(aWindow) {
@ -1542,6 +1542,8 @@ var BrowserApp = {
var NativeWindow = {
init: function() {
Services.obs.addObserver(this, "Menu:Clicked", false);
Services.obs.addObserver(this, "PageActions:Clicked", false);
Services.obs.addObserver(this, "PageActions:LongClicked", false);
Services.obs.addObserver(this, "Doorhanger:Reply", false);
Services.obs.addObserver(this, "Toast:Click", false);
Services.obs.addObserver(this, "Toast:Hidden", false);
@ -1550,6 +1552,8 @@ var NativeWindow = {
uninit: function() {
Services.obs.removeObserver(this, "Menu:Clicked");
Services.obs.removeObserver(this, "PageActions:Clicked");
Services.obs.removeObserver(this, "PageActions:LongClicked");
Services.obs.removeObserver(this, "Doorhanger:Reply");
Services.obs.removeObserver(this, "Toast:Click", false);
Services.obs.removeObserver(this, "Toast:Hidden", false);
@ -1595,6 +1599,35 @@ var NativeWindow = {
}
},
pageactions: {
_items: { },
add: function(aOptions) {
let id = uuidgen.generateUUID().toString();
sendMessageToJava({
gecko: {
type: "PageActions:Add",
id: id,
title: aOptions.title,
icon: aOptions.icon,
}
});
this._items[id] = {
clickCallback: aOptions.clickCallback,
longClickCallback: aOptions.longClickCallback
};
return id;
},
remove: function(id) {
sendMessageToJava({
gecko: {
type: "PageActions:Remove",
id: id
}
});
delete this._items[id];
}
},
menu: {
_callbacks: [],
_menuId: 1,
@ -1700,6 +1733,12 @@ var NativeWindow = {
} else if (aTopic == "Toast:Hidden") {
if (this.toast._callbacks[aData])
delete this.toast._callbacks[aData];
} else if (aTopic == "PageActions:Clicked") {
if (this.pageactions._items[aData].clickCallback)
this.pageactions._items[aData].clickCallback();
} else if (aTopic == "PageActions:LongClicked") {
if (this.pageactions._items[aData].longClickCallback)
this.pageactions._items[aData].longClickCallback();
} else if (aTopic == "Doorhanger:Reply") {
let data = JSON.parse(aData);
let reply_id = data["callback"];
@ -2498,6 +2537,8 @@ function Tab(aURL, aParams) {
this._fixedMarginTop = 0;
this._fixedMarginRight = 0;
this._fixedMarginBottom = 0;
this._readerEnabled = false;
this._readerActive = false;
this.userScrollPos = { x: 0, y: 0 };
this.viewportExcludesHorizontalMargins = true;
this.viewportExcludesVerticalMargins = true;
@ -2792,6 +2833,7 @@ Tab.prototype = {
this.browser.setAttribute("type", "content-primary");
this.browser.focus();
this.browser.docShellIsActive = true;
Reader.updatePageAction(this);
} else {
this.browser.setAttribute("type", "content-targetable");
this.browser.docShellIsActive = false;
@ -3484,9 +3526,12 @@ Tab.prototype = {
if (article == null || (article.url != tabURL)) {
// Don't clear the article for about:reader pages since we want to
// use the article from the previous page
if (!tabURL.startsWith("about:reader"))
if (!tabURL.startsWith("about:reader")) {
this.savedArticle = null;
this.readerEnabled = false;
} else {
this.readerActive = true;
}
return;
}
@ -3496,6 +3541,12 @@ Tab.prototype = {
type: "Content:ReaderEnabled",
tabID: this.id
});
if(this.readerActive)
this.readerActive = false;
if(!this.readerEnabled)
this.readerEnabled = true;
}.bind(this));
}
}
@ -3951,6 +4002,26 @@ Tab.prototype = {
}
},
set readerEnabled(isReaderEnabled) {
this._readerEnabled = isReaderEnabled;
if (this.getActive())
Reader.updatePageAction(this);
},
get readerEnabled() {
return this._readerEnabled;
},
set readerActive(isReaderActive) {
this._readerActive = isReaderActive;
if (this.getActive())
Reader.updatePageAction(this);
},
get readerActive() {
return this._readerActive;
},
// nsIBrowserTab
get window() {
if (!this.browser)
@ -6889,6 +6960,46 @@ let Reader = {
Services.prefs.addObserver("reader.parse-on-load.", this, false);
},
pageAction: {
readerModeCallback: function(){
sendMessageToJava({
gecko: {
type: "Reader:Click",
}
});
},
readerModeActiveCallback: function(){
sendMessageToJava({
gecko: {
type: "Reader:LongClick",
}
});
},
},
updatePageAction: function(tab) {
if(this.pageAction.id) {
NativeWindow.pageactions.remove(this.pageAction.id);
delete this.pageAction.id;
}
if (tab.readerActive) {
this.pageAction.id = NativeWindow.pageactions.add({
title: Strings.browser.GetStringFromName("readerMode.exit"),
icon: "drawable://reader_active",
clickCallback: this.pageAction.readerModeCallback
});
} else if (tab.readerEnabled) {
this.pageAction.id = NativeWindow.pageactions.add({
title: Strings.browser.GetStringFromName("readerMode.enter"),
icon: "drawable://reader",
clickCallback:this.pageAction.readerModeCallback,
longClickCallback: this.pageAction.readerModeActiveCallback
});
}
},
observe: function(aMessage, aTopic, aData) {
switch(aTopic) {
case "Reader:Add": {

View File

@ -268,3 +268,7 @@ getUserMedia.audioDevice.prompt = Microphone to use
getUserMedia.sharingCamera.message2 = Camera is on
getUserMedia.sharingMicrophone.message2 = Microphone is on
getUserMedia.sharingCameraAndMicrophone.message2 = Camera and microphone are on
#Reader mode
readerMode.enter = Enter Reader Mode
readerMode.exit = Exit Reader Mode