mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-14 05:44:20 +00:00
Bug 736321 - Support HTML Context menus in Fennec. r=mfinkle
This commit is contained in:
parent
14606784d8
commit
2603a6e9a2
@ -558,6 +558,7 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC
|
||||
public boolean inGroup = false;
|
||||
public boolean disabled = false;
|
||||
public int id = 0;
|
||||
public boolean isParent = false;
|
||||
|
||||
// This member can't be accessible from JS, see bug 733749.
|
||||
public Drawable icon = null;
|
||||
@ -568,6 +569,7 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC
|
||||
try { inGroup = aObject.getBoolean("inGroup"); } catch(Exception ex) { }
|
||||
try { disabled = aObject.getBoolean("disabled"); } catch(Exception ex) { }
|
||||
try { id = aObject.getInt("id"); } catch(Exception ex) { }
|
||||
try { isParent = aObject.getBoolean("isParent"); } catch(Exception ex) { }
|
||||
}
|
||||
|
||||
public PromptListItem(String aLabel) {
|
||||
@ -599,53 +601,62 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC
|
||||
}
|
||||
|
||||
private void maybeUpdateIcon(PromptListItem item, TextView t) {
|
||||
if (item.icon == null)
|
||||
if (item.icon == null && !item.isParent) {
|
||||
t.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
Drawable d = null;
|
||||
Resources res = GeckoApp.mAppContext.getResources();
|
||||
|
||||
// Set padding inside the item.
|
||||
t.setPadding(item.inGroup ? mLeftRightTextWithIconPadding + mGroupPaddingSize :
|
||||
mLeftRightTextWithIconPadding,
|
||||
mTopBottomTextWithIconPadding,
|
||||
mLeftRightTextWithIconPadding,
|
||||
mTopBottomTextWithIconPadding);
|
||||
|
||||
// Set the padding between the icon and the text.
|
||||
t.setCompoundDrawablePadding(mIconTextPadding);
|
||||
if (item.icon != null) {
|
||||
// Set padding inside the item.
|
||||
t.setPadding(item.inGroup ? mLeftRightTextWithIconPadding + mGroupPaddingSize :
|
||||
mLeftRightTextWithIconPadding,
|
||||
mTopBottomTextWithIconPadding,
|
||||
mLeftRightTextWithIconPadding,
|
||||
mTopBottomTextWithIconPadding);
|
||||
// We want the icon to be of a specific size. Some do not
|
||||
// follow this rule so we have to resize them.
|
||||
Bitmap bitmap = ((BitmapDrawable) item.icon).getBitmap();
|
||||
d = new BitmapDrawable(Bitmap.createScaledBitmap(bitmap, mIconSize, mIconSize, true));
|
||||
}
|
||||
|
||||
// We want the icon to be of a specific size. Some do not
|
||||
// follow this rule so we have to resize them.
|
||||
Bitmap bitmap = ((BitmapDrawable) item.icon).getBitmap();
|
||||
Drawable d = new BitmapDrawable(Bitmap.createScaledBitmap(bitmap, mIconSize, mIconSize, true));
|
||||
Drawable moreDrawable = null;
|
||||
if (item.isParent) {
|
||||
moreDrawable = res.getDrawable(android.R.drawable.ic_menu_more);
|
||||
}
|
||||
|
||||
t.setCompoundDrawablesWithIntrinsicBounds(d, null, null, null);
|
||||
if (d != null || moreDrawable != null) {
|
||||
t.setCompoundDrawablesWithIntrinsicBounds(d, null, moreDrawable, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeUpdateCheckedState(int position, PromptListItem item, ViewHolder viewHolder) {
|
||||
if (item.isGroup || mSelected == null)
|
||||
viewHolder.textView.setPadding((item.inGroup ? mGroupPaddingSize : viewHolder.paddingLeft),
|
||||
viewHolder.paddingTop,
|
||||
viewHolder.paddingRight,
|
||||
viewHolder.paddingBottom);
|
||||
|
||||
viewHolder.textView.setEnabled(!item.disabled && !item.isGroup);
|
||||
viewHolder.textView.setClickable(item.isGroup || item.disabled);
|
||||
|
||||
if (mSelected == null)
|
||||
return;
|
||||
|
||||
CheckedTextView ct;
|
||||
try {
|
||||
ct = (CheckedTextView) viewHolder.textView;
|
||||
// Apparently just using ct.setChecked(true) doesn't work, so this
|
||||
// is stolen from the android source code as a way to set the checked
|
||||
// state of these items
|
||||
if (listView != null)
|
||||
listView.setItemChecked(position, mSelected[position]);
|
||||
} catch (Exception e) {
|
||||
return;
|
||||
}
|
||||
|
||||
ct.setEnabled(!item.disabled);
|
||||
ct.setClickable(item.disabled);
|
||||
|
||||
// Apparently just using ct.setChecked(true) doesn't work, so this
|
||||
// is stolen from the android source code as a way to set the checked
|
||||
// state of these items
|
||||
if (listView != null)
|
||||
listView.setItemChecked(position, mSelected[position]);
|
||||
|
||||
ct.setPadding((item.inGroup ? mGroupPaddingSize : viewHolder.paddingLeft),
|
||||
viewHolder.paddingTop,
|
||||
viewHolder.paddingRight,
|
||||
viewHolder.paddingBottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1381,6 +1381,7 @@ var NativeWindow = {
|
||||
},
|
||||
contextmenus: {
|
||||
items: {}, // a list of context menu items that we may show
|
||||
_nativeItemsSeparator: 0, // the index to insert native context menu items at
|
||||
_contextId: 0, // id to assign to new context menu items if they are added
|
||||
|
||||
init: function() {
|
||||
@ -1552,6 +1553,62 @@ var NativeWindow = {
|
||||
else this._targetRef = null;
|
||||
},
|
||||
|
||||
_addHTMLContextMenuItems: function cm_addContextMenuItems(aMenu, aParent) {
|
||||
for (let i = 0; i < aMenu.childNodes.length; i++) {
|
||||
let item = aMenu.childNodes[i];
|
||||
if (!item.label || item.hasAttribute("hidden"))
|
||||
continue;
|
||||
|
||||
let id = this._contextId++;
|
||||
let menuitem = {
|
||||
id: id,
|
||||
isGroup: false,
|
||||
callback: (function(aTarget, aX, aY) {
|
||||
// If this is a menu item, show a new context menu with the submenu in it
|
||||
if (item instanceof Ci.nsIDOMHTMLMenuElement) {
|
||||
this.menuitems = [];
|
||||
this._nativeItemsSeparator = 0;
|
||||
|
||||
this._addHTMLContextMenuItems(item, id);
|
||||
this._innerShow(aTarget, aX, aY);
|
||||
} else {
|
||||
// oltherwise just click the item
|
||||
item.click();
|
||||
}
|
||||
}).bind(this),
|
||||
|
||||
getValue: function(aElt) {
|
||||
return {
|
||||
icon: item.icon,
|
||||
label: item.label,
|
||||
id: id,
|
||||
isGroup: false,
|
||||
inGroup: false,
|
||||
disabled: item.disabled,
|
||||
isParent: item instanceof Ci.nsIDOMHTMLMenuElement
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.menuitems.splice(this._nativeItemsSeparator, 0, menuitem);
|
||||
this._nativeItemsSeparator++;
|
||||
}
|
||||
},
|
||||
|
||||
_getMenuItemForId: function(aId) {
|
||||
if (!this.menuitems)
|
||||
return null;
|
||||
|
||||
for (let i = 0; i < this.menuitems.length; i++) {
|
||||
if (this.menuitems[i].id == aId)
|
||||
return this.menuitems[i];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// Checks if there are context menu items to show, and if it finds them
|
||||
// sends a contextmenu event to content. We also send showing events to
|
||||
// any html5 context menus we are about to show
|
||||
_sendToContent: function(aX, aY) {
|
||||
// find and store the top most element this context menu is being shown for
|
||||
// use the highlighted element if possible, otherwise look for nearby clickable elements
|
||||
@ -1566,27 +1623,37 @@ var NativeWindow = {
|
||||
// store a weakref to the target to be used when the context menu event returns
|
||||
this._target = target;
|
||||
|
||||
this.menuitems = {};
|
||||
this.menuitems = [];
|
||||
let menuitemsSet = false;
|
||||
|
||||
// now walk up the tree and for each node look for any context menu items that apply
|
||||
let element = target;
|
||||
|
||||
this._nativeItemsSeparator = 0;
|
||||
while (element) {
|
||||
for each (let item in this.items) {
|
||||
if (!this.menuitems[item.id] && item.matches(element, aX, aY)) {
|
||||
this.menuitems[item.id] = item;
|
||||
menuitemsSet = true;
|
||||
// first check for any html5 context menus that might exist
|
||||
let contextmenu = element.contextMenu;
|
||||
if (contextmenu) {
|
||||
// send this before we build the list to make sure the site can update the menu
|
||||
contextmenu.QueryInterface(Components.interfaces.nsIHTMLMenu);
|
||||
contextmenu.sendShowEvent();
|
||||
this._addHTMLContextMenuItems(contextmenu, null);
|
||||
}
|
||||
|
||||
// then check for any context menu items registered in the ui
|
||||
for each (let item in this.items) {
|
||||
if (!this._getMenuItemForId(item.id) && item.matches(element, aX, aY)) {
|
||||
this.menuitems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// if we reach a link or a text node, stop digging up through the node hierarchy
|
||||
if (this.linkOpenableContext.matches(element) || this.textContext.matches(element))
|
||||
break;
|
||||
element = element.parentNode;
|
||||
}
|
||||
|
||||
// only send the contextmenu event to content if we are planning to show a context menu (i.e. not on every long tap)
|
||||
if (menuitemsSet) {
|
||||
if (this.menuitems.length > 0) {
|
||||
let event = target.ownerDocument.createEvent("MouseEvent");
|
||||
event.initMouseEvent("contextmenu", true, true, content,
|
||||
0, aX, aY, aX, aY, false, false, false, false,
|
||||
@ -1602,21 +1669,26 @@ var NativeWindow = {
|
||||
}
|
||||
},
|
||||
|
||||
// Actually shows the native context menu by passing a list of context menu items to
|
||||
// show to the Java.
|
||||
_show: function(aEvent) {
|
||||
let popupNode = this._target;
|
||||
this._target = null;
|
||||
if (aEvent.defaultPrevented || !popupNode) {
|
||||
return;
|
||||
}
|
||||
this._innerShow(popupNode, aEvent.clientX, aEvent.clientY);
|
||||
},
|
||||
|
||||
_innerShow: function(aTarget, aX, aY) {
|
||||
Haptic.performSimpleAction(Haptic.LongPress);
|
||||
|
||||
// spin through the tree looking for a title for this context menu
|
||||
let node = popupNode;
|
||||
let node = aTarget;
|
||||
let title ="";
|
||||
while(node && !title) {
|
||||
if (node.hasAttribute && node.hasAttribute("title")) {
|
||||
title = node.getAttribute("title")
|
||||
title = node.getAttribute("title");
|
||||
} else if ((node instanceof Ci.nsIDOMHTMLAnchorElement && node.href) ||
|
||||
(node instanceof Ci.nsIDOMHTMLAreaElement && node.href)) {
|
||||
title = this._getLinkURL(node);
|
||||
@ -1630,8 +1702,8 @@ var NativeWindow = {
|
||||
|
||||
// convert this.menuitems object to an array for sending to native code
|
||||
let itemArray = [];
|
||||
for each (let item in this.menuitems) {
|
||||
itemArray.push(item.getValue(popupNode));
|
||||
for (let i = 0; i < this.menuitems.length; i++) {
|
||||
itemArray.push(this.menuitems[i].getValue(aTarget));
|
||||
}
|
||||
|
||||
let msg = {
|
||||
@ -1643,18 +1715,25 @@ var NativeWindow = {
|
||||
};
|
||||
let data = JSON.parse(sendMessageToJava(msg));
|
||||
let selectedId = itemArray[data.button].id;
|
||||
let selectedItem = this.menuitems[selectedId];
|
||||
let selectedItem = this._getMenuItemForId(selectedId);
|
||||
|
||||
this.menuitems = null;
|
||||
if (selectedItem && selectedItem.callback) {
|
||||
while (popupNode) {
|
||||
if (selectedItem.matches(popupNode, aEvent.clientX, aEvent.clientY)) {
|
||||
selectedItem.callback.call(selectedItem, popupNode, aEvent.clientX, aEvent.clientY);
|
||||
break;
|
||||
if (selectedItem.matches) {
|
||||
// for menuitems added using the native UI, pass the dom element that matched that item to the callback
|
||||
while (aTarget) {
|
||||
if (selectedItem.matches(aTarget, aX, aY)) {
|
||||
selectedItem.callback.call(selectedItem, aTarget, aX, aY);
|
||||
foundNode = true;
|
||||
break;
|
||||
}
|
||||
aTarget = aTarget.parentNode;
|
||||
}
|
||||
popupNode = popupNode.parentNode;
|
||||
} else {
|
||||
// if this was added using the html5 context menu api, just click on the context menu item
|
||||
selectedItem.callback.call(selectedItem, aTarget, aX, aY);
|
||||
}
|
||||
}
|
||||
this.menuitems = null;
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user