summaryrefslogtreecommitdiffstats
path: root/widget/gtk/nsMenuItem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gtk/nsMenuItem.cpp')
-rw-r--r--widget/gtk/nsMenuItem.cpp712
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;
+}