1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Josh Aas <josh@mozilla.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsMenuItemX.h"
40 #include "nsMenuBarX.h"
42 #include "nsMenuItemIconX.h"
43 #include "nsMenuUtilsX.h"
45 #include "nsObjCExceptions.h"
48 #include "nsWidgetAtoms.h"
49 #include "nsGUIEvent.h"
51 #include "mozilla/dom/Element.h"
52 #include "nsIWidget.h"
53 #include "nsIDocument.h"
54 #include "nsIDOMDocument.h"
55 #include "nsIPrivateDOMEvent.h"
56 #include "nsIDOMEventTarget.h"
57 #include "nsIDOMDocumentEvent.h"
58 #include "nsIDOMElement.h"
60 nsMenuItemX::nsMenuItemX()
62 mType = eRegularMenuItemType;
63 mNativeMenuItem = nil;
65 mMenuGroupOwner = nsnull;
66 mIsChecked = PR_FALSE;
68 MOZ_COUNT_CTOR(nsMenuItemX);
71 nsMenuItemX::~nsMenuItemX()
73 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
75 // Prevent the icon object from outliving us.
79 // autorelease the native menu item so that anything else happening to this
80 // object happens before the native menu item actually dies
81 [mNativeMenuItem autorelease];
84 mMenuGroupOwner->UnregisterForContentChanges(mContent);
86 mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
88 MOZ_COUNT_DTOR(nsMenuItemX);
90 NS_OBJC_END_TRY_ABORT_BLOCK;
93 nsresult nsMenuItemX::Create(nsMenuX* aParent, const nsString& aLabel, EMenuItemType aItemType,
94 nsMenuGroupOwnerX* aMenuGroupOwner, nsIContent* aNode)
96 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
99 mMenuParent = aParent;
102 mMenuGroupOwner = aMenuGroupOwner;
103 NS_ASSERTION(mMenuGroupOwner, "No menu owner given, must have one!");
105 mMenuGroupOwner->RegisterForContentChanges(mContent, this);
107 nsIDocument *doc = mContent->GetCurrentDoc();
109 // if we have a command associated with this menu item, register for changes
110 // to the command DOM node
112 nsAutoString ourCommand;
113 mContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::command, ourCommand);
115 if (!ourCommand.IsEmpty()) {
116 nsIContent *commandElement = doc->GetElementById(ourCommand);
118 if (commandElement) {
119 mCommandContent = commandElement;
120 // register to observe the command DOM element
121 mMenuGroupOwner->RegisterForContentChanges(mCommandContent, this);
126 // decide enabled state based on command content if it exists, otherwise do it based
127 // on our own content
130 isEnabled = !mCommandContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::disabled, nsWidgetAtoms::_true, eCaseMatters);
132 isEnabled = !mContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::disabled, nsWidgetAtoms::_true, eCaseMatters);
134 // set up the native menu item
135 if (mType == eSeparatorMenuItemType) {
136 mNativeMenuItem = [[NSMenuItem separatorItem] retain];
139 NSString *newCocoaLabelString = nsMenuUtilsX::GetTruncatedCocoaLabel(aLabel);
140 mNativeMenuItem = [[NSMenuItem alloc] initWithTitle:newCocoaLabelString action:nil keyEquivalent:@""];
142 [mNativeMenuItem setEnabled:(BOOL)isEnabled];
144 SetChecked(mContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::checked,
145 nsWidgetAtoms::_true, eCaseMatters));
147 // Set key shortcut and modifiers
149 nsAutoString keyValue;
150 mContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::key, keyValue);
151 if (!keyValue.IsEmpty()) {
152 nsIContent *keyContent = doc->GetElementById(keyValue);
154 nsAutoString keyChar(NS_LITERAL_STRING(" "));
155 keyContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::key, keyChar);
157 nsAutoString modifiersStr;
158 keyContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::modifiers, modifiersStr);
159 PRUint8 modifiers = nsMenuUtilsX::GeckoModifiersForNodeAttribute(modifiersStr);
161 SetKeyEquiv(modifiers, keyChar);
167 mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
169 return NS_ERROR_OUT_OF_MEMORY;
173 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
176 nsresult nsMenuItemX::SetChecked(PRBool aIsChecked)
178 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
180 mIsChecked = aIsChecked;
182 // update the content model. This will also handle unchecking our siblings
183 // if we are a radiomenu
184 mContent->SetAttr(kNameSpaceID_None, nsWidgetAtoms::checked,
185 mIsChecked ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"), PR_TRUE);
187 // update native menu item
189 [mNativeMenuItem setState:NSOnState];
191 [mNativeMenuItem setState:NSOffState];
195 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
198 EMenuItemType nsMenuItemX::GetMenuItemType()
203 // Executes the "cached" javaScript command.
204 // Returns NS_OK if the command was executed properly, otherwise an error code.
205 void nsMenuItemX::DoCommand()
207 // flip "checked" state if we're a checkbox menu, or an un-checked radio menu
208 if (mType == eCheckboxMenuItemType ||
209 (mType == eRadioMenuItemType && !mIsChecked)) {
210 if (!mContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::autocheck,
211 nsWidgetAtoms::_false, eCaseMatters))
212 SetChecked(!mIsChecked);
213 /* the AttributeChanged code will update all the internal state */
216 nsMenuUtilsX::DispatchCommandTo(mContent);
219 nsresult nsMenuItemX::DispatchDOMEvent(const nsString &eventName, PRBool *preventDefaultCalled)
222 return NS_ERROR_FAILURE;
224 // get owner document for content
225 nsCOMPtr<nsIDocument> parentDoc = mContent->GetOwnerDoc();
227 NS_WARNING("Failed to get owner nsIDocument for menu item content");
228 return NS_ERROR_FAILURE;
231 // get interface for creating DOM events from content owner document
232 nsCOMPtr<nsIDOMDocumentEvent> DOMEventFactory = do_QueryInterface(parentDoc);
233 if (!DOMEventFactory) {
234 NS_WARNING("Failed to QI parent nsIDocument to nsIDOMDocumentEvent");
235 return NS_ERROR_FAILURE;
239 nsCOMPtr<nsIDOMEvent> event;
240 nsresult rv = DOMEventFactory->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
242 NS_WARNING("Failed to create nsIDOMEvent");
245 event->InitEvent(eventName, PR_TRUE, PR_TRUE);
247 // mark DOM event as trusted
248 nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(event));
249 privateEvent->SetTrusted(PR_TRUE);
252 nsCOMPtr<nsIDOMEventTarget> eventTarget = do_QueryInterface(mContent);
253 rv = eventTarget->DispatchEvent(event, preventDefaultCalled);
255 NS_WARNING("Failed to send DOM event via nsIDOMEventTarget");
262 // Walk the sibling list looking for nodes with the same name and
264 void nsMenuItemX::UncheckRadioSiblings(nsIContent* inCheckedContent)
266 nsAutoString myGroupName;
267 inCheckedContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::name, myGroupName);
268 if (!myGroupName.Length()) // no groupname, nothing to do
271 nsCOMPtr<nsIContent> parent = inCheckedContent->GetParent();
275 // loop over siblings
276 PRUint32 count = parent->GetChildCount();
277 for (PRUint32 i = 0; i < count; i++) {
278 nsIContent *sibling = parent->GetChildAt(i);
280 if (sibling != inCheckedContent) { // skip this node
281 // if the current sibling is in the same group, clear it
282 if (sibling->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::name,
283 myGroupName, eCaseMatters))
284 sibling->SetAttr(kNameSpaceID_None, nsWidgetAtoms::checked, NS_LITERAL_STRING("false"), PR_TRUE);
290 void nsMenuItemX::SetKeyEquiv(PRUint8 aModifiers, const nsString &aText)
292 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
294 unsigned int macModifiers = nsMenuUtilsX::MacModifiersForGeckoModifiers(aModifiers);
295 [mNativeMenuItem setKeyEquivalentModifierMask:macModifiers];
297 NSString *keyEquivalent = [[NSString stringWithCharacters:(unichar*)aText.get()
298 length:aText.Length()] lowercaseString];
299 if ([keyEquivalent isEqualToString:@" "])
300 [mNativeMenuItem setKeyEquivalent:@""];
302 [mNativeMenuItem setKeyEquivalent:keyEquivalent];
304 NS_OBJC_END_TRY_ABORT_BLOCK;
312 nsMenuItemX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, nsIAtom *aAttribute)
314 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
319 if (aContent == mContent) { // our own content node changed
320 if (aAttribute == nsWidgetAtoms::checked) {
321 // if we're a radio menu, uncheck our sibling radio items. No need to
322 // do any of this if we're just a normal check menu.
323 if (mType == eRadioMenuItemType) {
324 if (mContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::checked,
325 nsWidgetAtoms::_true, eCaseMatters))
326 UncheckRadioSiblings(mContent);
328 mMenuParent->SetRebuild(PR_TRUE);
330 else if (aAttribute == nsWidgetAtoms::hidden ||
331 aAttribute == nsWidgetAtoms::collapsed ||
332 aAttribute == nsWidgetAtoms::label) {
333 mMenuParent->SetRebuild(PR_TRUE);
335 else if (aAttribute == nsWidgetAtoms::image) {
338 else if (aAttribute == nsWidgetAtoms::disabled) {
339 if (aContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::disabled, nsWidgetAtoms::_true, eCaseMatters))
340 [mNativeMenuItem setEnabled:NO];
342 [mNativeMenuItem setEnabled:YES];
345 else if (aContent == mCommandContent) {
346 // the only thing that really matters when the menu isn't showing is the
347 // enabled state since it enables/disables keyboard commands
348 if (aAttribute == nsWidgetAtoms::disabled) {
349 // first we sync our menu item DOM node with the command DOM node
350 nsAutoString commandDisabled;
351 nsAutoString menuDisabled;
352 aContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, commandDisabled);
353 mContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, menuDisabled);
354 if (!commandDisabled.Equals(menuDisabled)) {
355 // The menu's disabled state needs to be updated to match the command.
356 if (commandDisabled.IsEmpty())
357 mContent->UnsetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, PR_TRUE);
359 mContent->SetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, commandDisabled, PR_TRUE);
361 // now we sync our native menu item with the command DOM node
362 if (aContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::disabled, nsWidgetAtoms::_true, eCaseMatters))
363 [mNativeMenuItem setEnabled:NO];
365 [mNativeMenuItem setEnabled:YES];
369 NS_OBJC_END_TRY_ABORT_BLOCK;
372 void nsMenuItemX::ObserveContentRemoved(nsIDocument *aDocument, nsIContent *aChild, PRInt32 aIndexInContainer)
374 if (aChild == mCommandContent) {
375 mMenuGroupOwner->UnregisterForContentChanges(mCommandContent);
376 mCommandContent = nsnull;
379 mMenuParent->SetRebuild(PR_TRUE);
382 void nsMenuItemX::ObserveContentInserted(nsIDocument *aDocument, nsIContent *aChild, PRInt32 aIndexInContainer)
384 mMenuParent->SetRebuild(PR_TRUE);
387 void nsMenuItemX::SetupIcon()