Bug 698598: Stackable doorhangers in a scrolling pane [r=mfinkle]

This commit is contained in:
Sriram Ramasubramanian 2011-11-07 10:27:02 -08:00
parent 7e098f7158
commit b68e2d2abc
7 changed files with 250 additions and 136 deletions

View File

@ -20,6 +20,7 @@
*
* Contributor(s):
* Gian-Carlo Pascutto <gpascutto@mozilla.com>
* Sriram Ramasubramanian <sriram@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -37,77 +38,75 @@
package org.mozilla.gecko;
import java.util.ArrayList;
import android.util.Log;
import android.content.Context;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.Button;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
public class DoorHanger {
public class DoorHanger extends LinearLayout implements Button.OnClickListener {
private Context mContext;
public ArrayList<DoorHangerPopup> mPopups;
private LinearLayout mChoicesLayout;
private TextView mTextView;
private Button mButton;
private LayoutParams mLayoutParams;
public Tab mTab;
// value used to identify the notification
private String mValue;
private final int POPUP_VERTICAL_SPACING = 10;
private final int POPUP_VERTICAL_OFFSET = 10;
public DoorHanger(Context aContext, String aValue) {
super(aContext);
public DoorHanger(Context aContext) {
mContext = aContext;
mPopups = new ArrayList<DoorHangerPopup>();
mValue = aValue;
// Load layout into the custom view
LayoutInflater inflater =
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.doorhanger, this);
mTextView = (TextView) findViewById(R.id.doorhanger_title);
mChoicesLayout = (LinearLayout) findViewById(R.id.doorhanger_choices);
mLayoutParams = new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT,
1.0f);
}
public DoorHangerPopup getPopup(String value) {
// Check for duplicate popups
for (DoorHangerPopup dhp : mPopups) {
if (dhp.getValue().equals(value)) {
// Replace it
dhp = new DoorHangerPopup(mContext, value);
return dhp;
}
}
// No known popup like that, make new one
final DoorHangerPopup dhp = new DoorHangerPopup(mContext, value);
mPopups.add(dhp);
return dhp;
public void addButton(String aText, int aCallback) {
Button mButton = new Button(mContext);
mButton.setText(aText);
mButton.setTag(Integer.toString(aCallback));
mButton.setOnClickListener(this);
mChoicesLayout.addView(mButton, mLayoutParams);
}
public void removeForTab(int tabId) {
Log.i("DoorHanger", "removeForTab: " + tabId);
ArrayList<DoorHangerPopup> removeThis = new ArrayList<DoorHangerPopup>();
for (DoorHangerPopup dhp : mPopups) {
if (dhp.mTabId == tabId) {
removeThis.add(dhp);
}
}
for (DoorHangerPopup dhp : removeThis) {
removePopup(dhp);
}
public void onClick(View v) {
GeckoEvent e = new GeckoEvent("Doorhanger:Reply", v.getTag().toString());
GeckoAppShell.sendEventToGecko(e);
hidePopup();
GeckoApp.mDoorHangerPopup.removeDoorHanger(mTab, mValue);
}
public void removePopup(DoorHangerPopup dhp) {
dhp.setOnDismissListener(null);
dhp.dismiss();
mPopups.remove(dhp);
public void showPopup() {
setVisibility(View.VISIBLE);
}
public void hidePopup() {
setVisibility(View.GONE);
}
public void updateForTab(int tabId) {
Log.i("DoorHanger", "updateForTab: " + tabId);
int yOffset = POPUP_VERTICAL_OFFSET;
for (final DoorHangerPopup dhp : mPopups) {
if (dhp.mTabId == tabId) {
dhp.setOnDismissListener(new PopupWindow.OnDismissListener() {
@Override
public void onDismiss() {
removePopup(dhp);
}
});
dhp.showAtHeight(POPUP_VERTICAL_SPACING + yOffset);
yOffset += dhp.getHeight() + POPUP_VERTICAL_SPACING;
} else {
dhp.setOnDismissListener(null);
dhp.dismiss();
}
}
public String getValue() {
return mValue;
}
public void setText(String aText) {
mTextView.setText(aText);
}
public void setTab(Tab tab) {
mTab = tab;
}
}

View File

@ -20,6 +20,7 @@
*
* Contributor(s):
* Gian-Carlo Pascutto <gpascutto@mozilla.com>
* Sriram Ramasubramanian <sriram@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -37,82 +38,141 @@
package org.mozilla.gecko;
import java.util.HashMap;
import java.util.Iterator;
import android.content.Context;
import android.widget.TextView;
import android.widget.Button;
import android.widget.PopupWindow;
import android.view.Display;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.PopupWindow;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ScrollView;
public class DoorHangerPopup extends PopupWindow {
private Context mContext;
private LinearLayout mChoicesLayout;
private TextView mTextView;
private Button mButton;
private LayoutParams mLayoutParams;
private View popupView;
public int mTabId;
// value used to identify the notification
private String mValue;
private final int POPUP_VERTICAL_SIZE = 100;
private LinearLayout mContent;
public DoorHangerPopup(Context aContext, String aValue) {
public DoorHangerPopup(Context aContext) {
super(aContext);
mContext = aContext;
mValue = aValue;
LayoutInflater inflater =
(LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
popupView = (View) inflater.inflate(R.layout.doorhangerpopup, null);
setContentView(popupView);
mChoicesLayout = (LinearLayout) popupView.findViewById(R.id.doorhanger_choices);
mTextView = (TextView) popupView.findViewById(R.id.doorhanger_title);
mLayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT);
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ScrollView scrollContent = (ScrollView) inflater.inflate(R.layout.doorhangerpopup, null);
mContent = (LinearLayout) scrollContent.findViewById(R.id.doorhanger_container);
setContentView(scrollContent);
}
public void addButton(String aText, int aCallback) {
final String sCallback = Integer.toString(aCallback);
public DoorHanger addDoorHanger(Tab tab, String value) {
Log.i("DoorHangerPopup", "Adding a DoorHanger to Tab: " + tab.getId());
Button mButton = new Button(mContext);
mButton.setText(aText);
mButton.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
GeckoEvent e = new GeckoEvent("Doorhanger:Reply", sCallback);
GeckoAppShell.sendEventToGecko(e);
dismiss();
}
});
mChoicesLayout.addView(mButton, mLayoutParams);
DoorHanger dh = tab.getDoorHanger(value);
if (dh != null) {
dh.hidePopup();
tab.removeDoorHanger(value);
}
dh = new DoorHanger(mContent.getContext(), value);
dh.setTab(tab);
tab.addDoorHanger(value, dh);
mContent.addView(dh);
return dh;
}
public String getValue() {
return mValue;
public void removeDoorHanger(Tab tab, String value) {
Log.i("DoorHangerPopup", "Removing a DoorHanger from Tab: " + tab.getId());
tab.removeDoorHanger(value);
if (tab.getDoorHangers().size() == 0)
hide();
}
public void setText(String aText) {
mTextView.setText(aText);
public void showDoorHanger(DoorHanger dh) {
if (dh == null)
return;
dh.showPopup();
show();
}
public void setTab(int tabId) {
mTabId = tabId;
public void hideDoorHanger(DoorHanger dh) {
if (dh == null)
return;
dh.hidePopup();
show();
}
public void showAtHeight(int y) {
Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
public void hideAllDoorHangers() {
for (int i=0; i < mContent.getChildCount(); i++) {
DoorHanger dh = (DoorHanger) mContent.getChildAt(i);
dh.hidePopup();
}
int width = display.getWidth();
int height = display.getHeight();
showAsDropDown(popupView);
update(0, height - POPUP_VERTICAL_SIZE - y,
width, POPUP_VERTICAL_SIZE);
hide();
}
public void hide() {
if (isShowing()) {
Log.i("DoorHangerPopup", "Dismissing the DoorHangerPopup");
dismiss();
}
}
public void show() {
Log.i("DoorHangerPopup", "Showing the DoorHangerPopup");
if (isShowing())
update();
else
showAsDropDown(GeckoApp.mBrowserToolbar);
}
public void removeForTab(Tab tab) {
Log.i("DoorHangerPopup", "Removing all doorhangers for tab: " + tab.getId());
tab.removeAllDoorHangers();
}
public void showForTab(Tab tab) {
Log.i("DoorHangerPopup", "Showing all doorhangers for tab: " + tab.getId());
HashMap<String, DoorHanger> doorHangers = tab.getDoorHangers();
if (doorHangers == null) {
hide();
return;
}
hideAllDoorHangers();
DoorHanger dh;
Iterator keys = doorHangers.keySet().iterator();
while (keys.hasNext()) {
dh = (DoorHanger) doorHangers.get(keys.next());
dh.showPopup();
}
if (doorHangers.size() > 0)
show();
else
hide();
}
public void hideForTab(Tab tab) {
Log.i("DoorHangerPopup", "Hiding all doorhangers for tab: " + tab.getId());
HashMap<String, DoorHanger> doorHangers = tab.getDoorHangers();
if (doorHangers == null)
return;
DoorHanger dh;
Iterator keys = doorHangers.keySet().iterator();
while (keys.hasNext()) {
dh = (DoorHanger) doorHangers.get(keys.next());
dh.hidePopup();
}
}
}

View File

@ -97,8 +97,8 @@ abstract public class GeckoApp
public Handler mMainHandler;
private IntentFilter mConnectivityFilter;
private BroadcastReceiver mConnectivityReceiver;
private BrowserToolbar mBrowserToolbar;
public DoorHanger mDoorHanger;
public static BrowserToolbar mBrowserToolbar;
public static DoorHangerPopup mDoorHangerPopup;
public Favicons mFavicons;
private static boolean sIsGeckoReady = false;
private IntentFilter mBatteryFilter;
@ -621,14 +621,15 @@ abstract public class GeckoApp
tab.updateFavicon(null);
tab.updateFaviconURL(null);
tab.updateSecurityMode("unknown");
mDoorHangerPopup.removeForTab(tab);
mMainHandler.post(new Runnable() {
public void run() {
mAppContext.mDoorHanger.removeForTab(tabId);
if (Tabs.getInstance().isSelectedTab(tab)) {
mBrowserToolbar.setTitle(uri);
mBrowserToolbar.setFavicon(null);
mBrowserToolbar.setSecurityMode("unknown");
mDoorHangerPopup.hideAllDoorHangers();
}
}
});
@ -808,9 +809,9 @@ abstract public class GeckoApp
mMainHandler.post(new Runnable() {
public void run() {
DoorHangerPopup dhp =
mAppContext.mDoorHanger.getPopup(value);
dhp.setTab(tabId);
Tab tab = Tabs.getInstance().getTab(tabId);
DoorHanger dh = mAppContext.mDoorHangerPopup.addDoorHanger(tab, value);
for (int i = 0; i < buttons.length(); i++) {
JSONObject jo;
String label;
@ -821,39 +822,41 @@ abstract public class GeckoApp
callBackId = jo.getInt("callback");
Log.i(LOG_NAME, "Label: " + label
+ " CallbackId: " + callBackId);
dhp.addButton(label, callBackId);
dh.addButton(label, callBackId);
} catch (JSONException e) {
Log.i(LOG_NAME, "JSON throws " + e);
}
}
dhp.setText(msg);
dh.setText(msg);
// Show doorhanger if it is on the active tab
int activeTab = Tabs.getInstance().getSelectedTabId();
mAppContext.mDoorHanger.updateForTab(activeTab);
if (Tabs.getInstance().isSelectedTab(tab))
mAppContext.mDoorHangerPopup.showDoorHanger(dh);
}
});
}
void handleAddTab(final int tabId, final String uri) {
Tab tab = Tabs.getInstance().addTab(tabId, uri);
final Tab tab = Tabs.getInstance().addTab(tabId, uri);
mMainHandler.post(new Runnable() {
public void run() {
onTabsChanged();
mBrowserToolbar.updateTabs(Tabs.getInstance().getCount());
mDoorHanger.updateForTab(tabId);
mDoorHangerPopup.showForTab(tab);
}
});
}
void handleCloseTab(final int tabId) {
final Tab tab = Tabs.getInstance().getTab(tabId);
Tabs.getInstance().removeTab(tabId);
mDoorHangerPopup.removeForTab(tab);
mMainHandler.post(new Runnable() {
public void run() {
onTabsChanged();
mBrowserToolbar.updateTabs(Tabs.getInstance().getCount());
mDoorHanger.removeForTab(tabId);
mDoorHangerPopup.hideForTab(tab);
}
});
}
@ -870,7 +873,7 @@ abstract public class GeckoApp
mBrowserToolbar.setFavicon(tab.getFavicon());
mBrowserToolbar.setSecurityMode(tab.getSecurityMode());
mBrowserToolbar.setProgressVisibility(tab.isLoading());
mDoorHanger.updateForTab(tab.getId());
mDoorHangerPopup.showForTab(tab);
}
}
});
@ -1045,7 +1048,7 @@ abstract public class GeckoApp
mMainLayout = (LinearLayout) findViewById(R.id.main_layout);
mBrowserToolbar = (BrowserToolbar) findViewById(R.id.browser_toolbar);
mDoorHanger = new DoorHanger(this);
mDoorHangerPopup = new DoorHangerPopup(this);
Tabs tabs = Tabs.getInstance();
Tab tab = tabs.getSelectedTab();
@ -1507,6 +1510,7 @@ abstract public class GeckoApp
if (tab == null)
return false;
mDoorHangerPopup.hideAllDoorHangers();
return tab.doReload();
}

View File

@ -143,6 +143,7 @@ RES_LAYOUT = \
res/layout/awesomebar_tabs.xml \
res/layout/browser_toolbar.xml \
res/layout/doorhangerpopup.xml \
res/layout/doorhanger.xml \
res/layout/gecko_app.xml \
res/layout/gecko_menu.xml \
res/layout/launch_app_list.xml \

View File

@ -54,6 +54,7 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class Tab {
@ -70,6 +71,7 @@ public class Tab {
private int mHistoryIndex;
private boolean mLoading;
private boolean mBookmark;
private HashMap<String, DoorHanger> mDoorHangers;
static class HistoryEntry {
public final String mUri;
@ -96,6 +98,7 @@ public class Tab {
mHistory = new ArrayList<HistoryEntry>();
mHistoryIndex = -1;
mBookmark = false;
mDoorHangers = new HashMap<String, DoorHanger>();
}
public int getId() {
@ -213,6 +216,7 @@ public class Tab {
return false;
GeckoEvent e = new GeckoEvent("Session:Reload", "");
GeckoAppShell.sendEventToGecko(e);
removeAllDoorHangers();
return true;
}
@ -222,6 +226,7 @@ public class Tab {
}
GeckoEvent e = new GeckoEvent("Session:Back", "");
GeckoAppShell.sendEventToGecko(e);
removeAllDoorHangers();
return true;
}
@ -235,9 +240,36 @@ public class Tab {
}
GeckoEvent e = new GeckoEvent("Session:Forward", "");
GeckoAppShell.sendEventToGecko(e);
removeAllDoorHangers();
return true;
}
public void addDoorHanger(String value, DoorHanger dh) {
mDoorHangers.put(value, dh);
}
public void removeDoorHanger(String value) {
mDoorHangers.remove(value);
}
public void removeAllDoorHangers() {
mDoorHangers = new HashMap<String, DoorHanger> ();
}
public DoorHanger getDoorHanger(String value) {
if (mDoorHangers == null)
return null;
if (mDoorHangers.containsKey(value))
return mDoorHangers.get(value);
return null;
}
public HashMap<String, DoorHanger> getDoorHangers() {
return mDoorHangers;
}
void handleSessionHistoryMessage(String event, JSONObject message) throws JSONException {
if (event.equals("New")) {
final String uri = message.getString("uri");

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#3e3e3e">
<TextView android:id="@+id/doorhanger_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:padding="10dp"/>
<LinearLayout android:id="@+id/doorhanger_choices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"/>
</LinearLayout>

View File

@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView android:id="@+id/doorhanger_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<LinearLayout android:id="@+id/doorhanger_choices"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout android:id="@+id/doorhanger_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</ScrollView>