Mypal/widget/gtk/nsMenuItem.cpp

713 lines
24 KiB
C++

/* 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/. */
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/dom/Element.h"
#include "mozilla/Move.h"
#include "mozilla/Preferences.h"
#include "mozilla/TextEvents.h"
#include "nsAutoPtr.h"
#include "nsContentUtils.h"
#include "nsCRT.h"
#include "nsGkAtoms.h"
#include "nsGtkUtils.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIDOMDocument.h"
#include "nsIDOMEvent.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMKeyEvent.h"
#include "nsIDOMXULCommandEvent.h"
#include "nsIRunnable.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsStyleContext.h"
#include "nsThreadUtils.h"
#include "nsMenu.h"
#include "nsMenuBar.h"
#include "nsMenuContainer.h"
#include "nsNativeMenuDocListener.h"
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#if (MOZ_WIDGET_GTK == 3)
#include <gdk/gdkkeysyms-compat.h>
#endif
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#include "nsMenuItem.h"
using namespace mozilla;
struct KeyCodeData {
const char* str;
size_t strlength;
uint32_t keycode;
};
static struct KeyCodeData gKeyCodes[] = {
#define NS_DEFINE_VK(aDOMKeyName, aDOMKeyCode) \
{ #aDOMKeyName, sizeof(#aDOMKeyName) - 1, aDOMKeyCode },
#include "mozilla/VirtualKeyCodeList.h"
#undef NS_DEFINE_VK
{ nullptr, 0, 0 }
};
struct KeyPair {
uint32_t DOMKeyCode;
guint GDKKeyval;
};
//
// Netscape keycodes are defined in widget/public/nsGUIEvent.h
// GTK keycodes are defined in <gdk/gdkkeysyms.h>
//
static const KeyPair gKeyPairs[] = {
{ NS_VK_CANCEL, GDK_Cancel },
{ NS_VK_BACK, GDK_BackSpace },
{ NS_VK_TAB, GDK_Tab },
{ NS_VK_TAB, GDK_ISO_Left_Tab },
{ NS_VK_CLEAR, GDK_Clear },
{ NS_VK_RETURN, GDK_Return },
{ NS_VK_SHIFT, GDK_Shift_L },
{ NS_VK_SHIFT, GDK_Shift_R },
{ NS_VK_SHIFT, GDK_Shift_Lock },
{ NS_VK_CONTROL, GDK_Control_L },
{ NS_VK_CONTROL, GDK_Control_R },
{ NS_VK_ALT, GDK_Alt_L },
{ NS_VK_ALT, GDK_Alt_R },
{ NS_VK_META, GDK_Meta_L },
{ NS_VK_META, GDK_Meta_R },
// Assume that Super or Hyper is always mapped to physical Win key.
{ NS_VK_WIN, GDK_Super_L },
{ NS_VK_WIN, GDK_Super_R },
{ NS_VK_WIN, GDK_Hyper_L },
{ NS_VK_WIN, GDK_Hyper_R },
// GTK's AltGraph key is similar to Mac's Option (Alt) key. However,
// unfortunately, browsers on Mac are using NS_VK_ALT for it even though
// it's really different from Alt key on Windows.
// On the other hand, GTK's AltGrapsh keys are really different from
// Alt key. However, there is no AltGrapsh key on Windows. On Windows,
// both Ctrl and Alt keys are pressed internally when AltGr key is pressed.
// For some languages' users, AltGraph key is important, so, web
// applications on such locale may want to know AltGraph key press.
// Therefore, we should map AltGr keycode for them only on GTK.
{ NS_VK_ALTGR, GDK_ISO_Level3_Shift },
{ NS_VK_ALTGR, GDK_ISO_Level5_Shift },
// We assume that Mode_switch is always used for level3 shift.
{ NS_VK_ALTGR, GDK_Mode_switch },
{ NS_VK_PAUSE, GDK_Pause },
{ NS_VK_CAPS_LOCK, GDK_Caps_Lock },
{ NS_VK_KANA, GDK_Kana_Lock },
{ NS_VK_KANA, GDK_Kana_Shift },
{ NS_VK_HANGUL, GDK_Hangul },
// { NS_VK_JUNJA, GDK_XXX },
// { NS_VK_FINAL, GDK_XXX },
{ NS_VK_HANJA, GDK_Hangul_Hanja },
{ NS_VK_KANJI, GDK_Kanji },
{ NS_VK_ESCAPE, GDK_Escape },
{ NS_VK_CONVERT, GDK_Henkan },
{ NS_VK_NONCONVERT, GDK_Muhenkan },
// { NS_VK_ACCEPT, GDK_XXX },
// { NS_VK_MODECHANGE, GDK_XXX },
{ NS_VK_SPACE, GDK_space },
{ NS_VK_PAGE_UP, GDK_Page_Up },
{ NS_VK_PAGE_DOWN, GDK_Page_Down },
{ NS_VK_END, GDK_End },
{ NS_VK_HOME, GDK_Home },
{ NS_VK_LEFT, GDK_Left },
{ NS_VK_UP, GDK_Up },
{ NS_VK_RIGHT, GDK_Right },
{ NS_VK_DOWN, GDK_Down },
{ NS_VK_SELECT, GDK_Select },
{ NS_VK_PRINT, GDK_Print },
{ NS_VK_EXECUTE, GDK_Execute },
{ NS_VK_PRINTSCREEN, GDK_Print },
{ NS_VK_INSERT, GDK_Insert },
{ NS_VK_DELETE, GDK_Delete },
{ NS_VK_HELP, GDK_Help },
// keypad keys
{ NS_VK_LEFT, GDK_KP_Left },
{ NS_VK_RIGHT, GDK_KP_Right },
{ NS_VK_UP, GDK_KP_Up },
{ NS_VK_DOWN, GDK_KP_Down },
{ NS_VK_PAGE_UP, GDK_KP_Page_Up },
// Not sure what these are
//{ NS_VK_, GDK_KP_Prior },
//{ NS_VK_, GDK_KP_Next },
{ NS_VK_CLEAR, GDK_KP_Begin }, // Num-unlocked 5
{ NS_VK_PAGE_DOWN, GDK_KP_Page_Down },
{ NS_VK_HOME, GDK_KP_Home },
{ NS_VK_END, GDK_KP_End },
{ NS_VK_INSERT, GDK_KP_Insert },
{ NS_VK_DELETE, GDK_KP_Delete },
{ NS_VK_RETURN, GDK_KP_Enter },
{ NS_VK_NUM_LOCK, GDK_Num_Lock },
{ NS_VK_SCROLL_LOCK,GDK_Scroll_Lock },
// Function keys
{ NS_VK_F1, GDK_F1 },
{ NS_VK_F2, GDK_F2 },
{ NS_VK_F3, GDK_F3 },
{ NS_VK_F4, GDK_F4 },
{ NS_VK_F5, GDK_F5 },
{ NS_VK_F6, GDK_F6 },
{ NS_VK_F7, GDK_F7 },
{ NS_VK_F8, GDK_F8 },
{ NS_VK_F9, GDK_F9 },
{ NS_VK_F10, GDK_F10 },
{ NS_VK_F11, GDK_F11 },
{ NS_VK_F12, GDK_F12 },
{ NS_VK_F13, GDK_F13 },
{ NS_VK_F14, GDK_F14 },
{ NS_VK_F15, GDK_F15 },
{ NS_VK_F16, GDK_F16 },
{ NS_VK_F17, GDK_F17 },
{ NS_VK_F18, GDK_F18 },
{ NS_VK_F19, GDK_F19 },
{ NS_VK_F20, GDK_F20 },
{ NS_VK_F21, GDK_F21 },
{ NS_VK_F22, GDK_F22 },
{ NS_VK_F23, GDK_F23 },
{ NS_VK_F24, GDK_F24 },
// context menu key, keysym 0xff67, typically keycode 117 on 105-key (Microsoft)
// x86 keyboards, located between right 'Windows' key and right Ctrl key
{ NS_VK_CONTEXT_MENU, GDK_Menu },
{ NS_VK_SLEEP, GDK_Sleep },
{ NS_VK_ATTN, GDK_3270_Attn },
{ NS_VK_CRSEL, GDK_3270_CursorSelect },
{ NS_VK_EXSEL, GDK_3270_ExSelect },
{ NS_VK_EREOF, GDK_3270_EraseEOF },
{ NS_VK_PLAY, GDK_3270_Play },
//{ NS_VK_ZOOM, GDK_XXX },
{ NS_VK_PA1, GDK_3270_PA1 },
};
static guint
ConvertGeckoKeyNameToGDKKeyval(nsAString& aKeyName) {
NS_ConvertUTF16toUTF8 keyName(aKeyName);
ToUpperCase(keyName); // We want case-insensitive comparison with data
// stored as uppercase.
uint32_t keyCode = 0;
uint32_t keyNameLength = keyName.Length();
const char* keyNameStr = keyName.get();
for (uint16_t i = 0; i < ArrayLength(gKeyCodes); ++i) {
if (keyNameLength == gKeyCodes[i].strlength &&
!nsCRT::strcmp(gKeyCodes[i].str, keyNameStr)) {
keyCode = gKeyCodes[i].keycode;
break;
}
}
// First, try to handle alphanumeric input, not listed in nsKeycodes:
// most likely, more letters will be getting typed in than things in
// the key list, so we will look through these first.
if (keyCode >= NS_VK_A && keyCode <= NS_VK_Z) {
// gdk and DOM both use the ASCII codes for these keys.
return keyCode;
}
// numbers
if (keyCode >= NS_VK_0 && keyCode <= NS_VK_9) {
// gdk and DOM both use the ASCII codes for these keys.
return keyCode - NS_VK_0 + GDK_0;
}
switch (keyCode) {
// keys in numpad
case NS_VK_MULTIPLY: return GDK_KP_Multiply;
case NS_VK_ADD: return GDK_KP_Add;
case NS_VK_SEPARATOR: return GDK_KP_Separator;
case NS_VK_SUBTRACT: return GDK_KP_Subtract;
case NS_VK_DECIMAL: return GDK_KP_Decimal;
case NS_VK_DIVIDE: return GDK_KP_Divide;
case NS_VK_NUMPAD0: return GDK_KP_0;
case NS_VK_NUMPAD1: return GDK_KP_1;
case NS_VK_NUMPAD2: return GDK_KP_2;
case NS_VK_NUMPAD3: return GDK_KP_3;
case NS_VK_NUMPAD4: return GDK_KP_4;
case NS_VK_NUMPAD5: return GDK_KP_5;
case NS_VK_NUMPAD6: return GDK_KP_6;
case NS_VK_NUMPAD7: return GDK_KP_7;
case NS_VK_NUMPAD8: return GDK_KP_8;
case NS_VK_NUMPAD9: return GDK_KP_9;
// other prinable keys
case NS_VK_SPACE: return GDK_space;
case NS_VK_COLON: return GDK_colon;
case NS_VK_SEMICOLON: return GDK_semicolon;
case NS_VK_LESS_THAN: return GDK_less;
case NS_VK_EQUALS: return GDK_equal;
case NS_VK_GREATER_THAN: return GDK_greater;
case NS_VK_QUESTION_MARK: return GDK_question;
case NS_VK_AT: return GDK_at;
case NS_VK_CIRCUMFLEX: return GDK_asciicircum;
case NS_VK_EXCLAMATION: return GDK_exclam;
case NS_VK_DOUBLE_QUOTE: return GDK_quotedbl;
case NS_VK_HASH: return GDK_numbersign;
case NS_VK_DOLLAR: return GDK_dollar;
case NS_VK_PERCENT: return GDK_percent;
case NS_VK_AMPERSAND: return GDK_ampersand;
case NS_VK_UNDERSCORE: return GDK_underscore;
case NS_VK_OPEN_PAREN: return GDK_parenleft;
case NS_VK_CLOSE_PAREN: return GDK_parenright;
case NS_VK_ASTERISK: return GDK_asterisk;
case NS_VK_PLUS: return GDK_plus;
case NS_VK_PIPE: return GDK_bar;
case NS_VK_HYPHEN_MINUS: return GDK_minus;
case NS_VK_OPEN_CURLY_BRACKET: return GDK_braceleft;
case NS_VK_CLOSE_CURLY_BRACKET: return GDK_braceright;
case NS_VK_TILDE: return GDK_asciitilde;
case NS_VK_COMMA: return GDK_comma;
case NS_VK_PERIOD: return GDK_period;
case NS_VK_SLASH: return GDK_slash;
case NS_VK_BACK_QUOTE: return GDK_grave;
case NS_VK_OPEN_BRACKET: return GDK_bracketleft;
case NS_VK_BACK_SLASH: return GDK_backslash;
case NS_VK_CLOSE_BRACKET: return GDK_bracketright;
case NS_VK_QUOTE: return GDK_apostrophe;
}
// misc other things
for (uint32_t i = 0; i < ArrayLength(gKeyPairs); ++i) {
if (gKeyPairs[i].DOMKeyCode == keyCode) {
return gKeyPairs[i].GDKKeyval;
}
}
return 0;
}
class nsMenuItemUncheckSiblingsRunnable final : public Runnable {
public:
NS_IMETHODIMP Run() {
if (mMenuItem) {
static_cast<nsMenuItem* >(mMenuItem.get())->UncheckSiblings();
}
return NS_OK;
}
nsMenuItemUncheckSiblingsRunnable(nsMenuItem* aMenuItem) :
mMenuItem(aMenuItem) { };
private:
nsWeakMenuObject mMenuItem;
};
bool
nsMenuItem::IsCheckboxOrRadioItem() const {
return mType == eMenuItemType_Radio ||
mType == eMenuItemType_CheckBox;
}
/* static */ void
nsMenuItem::item_activated_cb(DbusmenuMenuitem* menuitem,
guint timestamp,
gpointer user_data) {
nsMenuItem* item = static_cast<nsMenuItem* >(user_data);
item->Activate(timestamp);
}
void
nsMenuItem::Activate(uint32_t aTimestamp) {
GdkWindow* window = gtk_widget_get_window(MenuBar()->TopLevelWindow());
gdk_x11_window_set_user_time(
window, std::min(aTimestamp, gdk_x11_get_server_time(window)));
// We do this to avoid mutating our view of the menu until
// after we have finished
nsNativeMenuDocListener::BlockUpdatesScope updatesBlocker;
if (!ContentNode()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
nsGkAtoms::_false, eCaseMatters) &&
(mType == eMenuItemType_CheckBox ||
(mType == eMenuItemType_Radio && !mIsChecked))) {
ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::checked,
mIsChecked ?
NS_LITERAL_STRING("false") : NS_LITERAL_STRING("true"),
true);
}
nsIDocument* doc = ContentNode()->OwnerDoc();
nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(ContentNode());
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
if (domDoc && target) {
nsCOMPtr<nsIDOMEvent> event;
domDoc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"),
getter_AddRefs(event));
nsCOMPtr<nsIDOMXULCommandEvent> command = do_QueryInterface(event);
if (command) {
command->InitCommandEvent(NS_LITERAL_STRING("command"),
true, true, doc->GetInnerWindow(), 0,
false, false, false, false, nullptr);
event->SetTrusted(true);
bool dummy;
target->DispatchEvent(event, &dummy);
}
}
// This kinda sucks, but Unity doesn't send a closed event
// after activating a menuitem
nsMenuObject* ancestor = Parent();
while (ancestor && ancestor->Type() == eType_Menu) {
static_cast<nsMenu* >(ancestor)->OnClose();
ancestor = ancestor->Parent();
}
}
void
nsMenuItem::CopyAttrFromNodeIfExists(nsIContent* aContent, nsIAtom* aAttribute) {
nsAutoString value;
if (aContent->GetAttr(kNameSpaceID_None, aAttribute, value)) {
ContentNode()->SetAttr(kNameSpaceID_None, aAttribute, value, true);
}
}
void
nsMenuItem::UpdateState() {
if (!IsCheckboxOrRadioItem()) {
return;
}
mIsChecked = ContentNode()->AttrValueIs(kNameSpaceID_None,
nsGkAtoms::checked,
nsGkAtoms::_true,
eCaseMatters);
dbusmenu_menuitem_property_set_int(GetNativeData(),
DBUSMENU_MENUITEM_PROP_TOGGLE_STATE,
mIsChecked ?
DBUSMENU_MENUITEM_TOGGLE_STATE_CHECKED :
DBUSMENU_MENUITEM_TOGGLE_STATE_UNCHECKED);
}
void
nsMenuItem::UpdateTypeAndState() {
static nsIContent::AttrValuesArray attrs[] =
{ &nsGkAtoms::checkbox, &nsGkAtoms::radio, nullptr };
int32_t type = ContentNode()->FindAttrValueIn(kNameSpaceID_None,
nsGkAtoms::type,
attrs, eCaseMatters);
if (type >= 0 && type < 2) {
if (type == 0) {
dbusmenu_menuitem_property_set(GetNativeData(),
DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
DBUSMENU_MENUITEM_TOGGLE_CHECK);
mType = eMenuItemType_CheckBox;
} else if (type == 1) {
dbusmenu_menuitem_property_set(GetNativeData(),
DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE,
DBUSMENU_MENUITEM_TOGGLE_RADIO);
mType = eMenuItemType_Radio;
}
UpdateState();
} else {
dbusmenu_menuitem_property_remove(GetNativeData(),
DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE);
dbusmenu_menuitem_property_remove(GetNativeData(),
DBUSMENU_MENUITEM_PROP_TOGGLE_STATE);
mType = eMenuItemType_Normal;
}
}
void
nsMenuItem::UpdateAccel() {
nsIDocument* doc = ContentNode()->GetUncomposedDoc();
if (doc) {
nsCOMPtr<nsIContent> oldKeyContent;
oldKeyContent.swap(mKeyContent);
nsAutoString key;
ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::key, key);
if (!key.IsEmpty()) {
mKeyContent = doc->GetElementById(key);
}
if (mKeyContent != oldKeyContent) {
if (oldKeyContent) {
DocListener()->UnregisterForContentChanges(oldKeyContent);
}
if (mKeyContent) {
DocListener()->RegisterForContentChanges(mKeyContent, this);
}
}
}
if (!mKeyContent) {
dbusmenu_menuitem_property_remove(GetNativeData(),
DBUSMENU_MENUITEM_PROP_SHORTCUT);
return;
}
nsAutoString modifiers;
mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::modifiers, modifiers);
uint32_t modifier = 0;
if (!modifiers.IsEmpty()) {
char* str = ToNewUTF8String(modifiers);
char* token = strtok(str, ", \t");
while(token) {
if (nsCRT::strcmp(token, "shift") == 0) {
modifier |= GDK_SHIFT_MASK;
} else if (nsCRT::strcmp(token, "alt") == 0) {
modifier |= GDK_MOD1_MASK;
} else if (nsCRT::strcmp(token, "meta") == 0) {
modifier |= GDK_META_MASK;
} else if (nsCRT::strcmp(token, "control") == 0) {
modifier |= GDK_CONTROL_MASK;
} else if (nsCRT::strcmp(token, "accel") == 0) {
int32_t accel = Preferences::GetInt("ui.key.accelKey");
if (accel == nsIDOMKeyEvent::DOM_VK_META) {
modifier |= GDK_META_MASK;
} else if (accel == nsIDOMKeyEvent::DOM_VK_ALT) {
modifier |= GDK_MOD1_MASK;
} else {
modifier |= GDK_CONTROL_MASK;
}
}
token = strtok(nullptr, ", \t");
}
free(str);
}
nsAutoString keyStr;
mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::key, keyStr);
guint key = 0;
if (!keyStr.IsEmpty()) {
key = gdk_unicode_to_keyval(*keyStr.BeginReading());
}
if (key == 0) {
mKeyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::keycode, keyStr);
if (!keyStr.IsEmpty()) {
key = ConvertGeckoKeyNameToGDKKeyval(keyStr);
}
}
if (key == 0) {
key = GDK_VoidSymbol;
}
if (key != GDK_VoidSymbol) {
dbusmenu_menuitem_property_set_shortcut(GetNativeData(), key,
static_cast<GdkModifierType>(modifier));
} else {
dbusmenu_menuitem_property_remove(GetNativeData(),
DBUSMENU_MENUITEM_PROP_SHORTCUT);
}
}
nsMenuBar*
nsMenuItem::MenuBar() {
nsMenuObject* tmp = this;
while (tmp->Parent()) {
tmp = tmp->Parent();
}
MOZ_ASSERT(tmp->Type() == eType_MenuBar, "The top-level should be a menubar");
return static_cast<nsMenuBar* >(tmp);
}
void
nsMenuItem::UncheckSiblings() {
if (!ContentNode()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::radio, eCaseMatters)) {
// If we're not a radio button, we don't care
return;
}
nsAutoString name;
ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
nsIContent* parent = ContentNode()->GetParent();
if (!parent) {
return;
}
uint32_t count = parent->GetChildCount();
for (uint32_t i = 0; i < count; ++i) {
nsIContent* sibling = parent->GetChildAt(i);
nsAutoString otherName;
sibling->GetAttr(kNameSpaceID_None, nsGkAtoms::name, otherName);
if (sibling != ContentNode() && otherName == name &&
sibling->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
nsGkAtoms::radio, eCaseMatters)) {
sibling->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
}
}
}
void
nsMenuItem::InitializeNativeData() {
g_signal_connect(G_OBJECT(GetNativeData()),
DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED,
G_CALLBACK(item_activated_cb), this);
mNeedsUpdate = true;
}
void
nsMenuItem::UpdateContentAttributes() {
nsIDocument* doc = ContentNode()->GetUncomposedDoc();
if (!doc) {
return;
}
nsAutoString command;
ContentNode()->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
if (command.IsEmpty()) {
return;
}
nsCOMPtr<nsIContent> commandContent = doc->GetElementById(command);
if (!commandContent) {
return;
}
if (commandContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
nsGkAtoms::_true, eCaseMatters)) {
ContentNode()->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
NS_LITERAL_STRING("true"), true);
} else {
ContentNode()->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
}
CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::checked);
CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::accesskey);
CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::label);
CopyAttrFromNodeIfExists(commandContent, nsGkAtoms::hidden);
}
void
nsMenuItem::Update(nsStyleContext* aStyleContext) {
if (mNeedsUpdate) {
mNeedsUpdate = false;
UpdateTypeAndState();
UpdateAccel();
UpdateLabel();
UpdateSensitivity();
}
UpdateVisibility(aStyleContext);
UpdateIcon(aStyleContext);
}
bool
nsMenuItem::IsCompatibleWithNativeData(DbusmenuMenuitem* aNativeData) const {
return nsCRT::strcmp(dbusmenu_menuitem_property_get(aNativeData,
DBUSMENU_MENUITEM_PROP_TYPE),
"separator") != 0;
}
nsMenuObject::PropertyFlags
nsMenuItem::SupportedProperties() const {
return static_cast<nsMenuObject::PropertyFlags>(
nsMenuObject::ePropLabel |
nsMenuObject::ePropEnabled |
nsMenuObject::ePropVisible |
nsMenuObject::ePropIconData |
nsMenuObject::ePropShortcut |
nsMenuObject::ePropToggleType |
nsMenuObject::ePropToggleState
);
}
void
nsMenuItem::OnAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute) {
MOZ_ASSERT(aContent == ContentNode() || aContent == mKeyContent,
"Received an event that wasn't meant for us!");
if (aContent == ContentNode() && aAttribute == nsGkAtoms::checked &&
aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
nsGkAtoms::_true, eCaseMatters)) {
nsContentUtils::AddScriptRunner(
new nsMenuItemUncheckSiblingsRunnable(this));
}
if (mNeedsUpdate) {
return;
}
if (!Parent()->IsBeingDisplayed()) {
mNeedsUpdate = true;
return;
}
if (aContent == ContentNode()) {
if (aAttribute == nsGkAtoms::key) {
UpdateAccel();
} else if (aAttribute == nsGkAtoms::label ||
aAttribute == nsGkAtoms::accesskey ||
aAttribute == nsGkAtoms::crop) {
UpdateLabel();
} else if (aAttribute == nsGkAtoms::disabled) {
UpdateSensitivity();
} else if (aAttribute == nsGkAtoms::type) {
UpdateTypeAndState();
} else if (aAttribute == nsGkAtoms::checked) {
UpdateState();
} else if (aAttribute == nsGkAtoms::hidden ||
aAttribute == nsGkAtoms::collapsed) {
RefPtr<nsStyleContext> sc = GetStyleContext();
UpdateVisibility(sc);
} else if (aAttribute == nsGkAtoms::image) {
RefPtr<nsStyleContext> sc = GetStyleContext();
UpdateIcon(sc);
}
} else if (aContent == mKeyContent &&
(aAttribute == nsGkAtoms::key ||
aAttribute == nsGkAtoms::keycode ||
aAttribute == nsGkAtoms::modifiers)) {
UpdateAccel();
}
}
nsMenuItem::nsMenuItem(nsMenuContainer* aParent, nsIContent* aContent) :
nsMenuObject(aParent, aContent),
mType(eMenuItemType_Normal),
mIsChecked(false),
mNeedsUpdate(false) {
MOZ_COUNT_CTOR(nsMenuItem);
}
nsMenuItem::~nsMenuItem() {
if (DocListener() && mKeyContent) {
DocListener()->UnregisterForContentChanges(mKeyContent);
}
if (GetNativeData()) {
g_signal_handlers_disconnect_by_func(GetNativeData(),
FuncToGpointer(item_activated_cb),
this);
}
MOZ_COUNT_DTOR(nsMenuItem);
}
nsMenuObject::EType
nsMenuItem::Type() const {
return eType_MenuItem;
}