diff options
Diffstat (limited to 'widget/gtk/nsMenuItem.cpp')
-rw-r--r-- | widget/gtk/nsMenuItem.cpp | 712 |
1 files changed, 712 insertions, 0 deletions
diff --git a/widget/gtk/nsMenuItem.cpp b/widget/gtk/nsMenuItem.cpp new file mode 100644 index 000000000..00cc5477c --- /dev/null +++ b/widget/gtk/nsMenuItem.cpp @@ -0,0 +1,712 @@ +/* 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; +} |