CLOSED TREE: TraceMonkey merge head. (a=blockers)
[mozilla-central.git] / widget / src / cocoa / nsMenuItemX.mm
blobe766c0eb5c21840007768a0fbf48611565a2a522
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
4  *
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/
9  *
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
13  * License.
14  *
15  * The Original Code is mozilla.org code.
16  *
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.
21  *
22  * Contributor(s):
23  *   Josh Aas <josh@mozilla.com>
24  *
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.
36  *
37  * ***** END LICENSE BLOCK ***** */
39 #include "nsMenuItemX.h"
40 #include "nsMenuBarX.h"
41 #include "nsMenuX.h"
42 #include "nsMenuItemIconX.h"
43 #include "nsMenuUtilsX.h"
45 #include "nsObjCExceptions.h"
47 #include "nsCOMPtr.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;
64   mMenuParent     = nsnull;
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.
76   if (mIcon)
77     mIcon->Destroy();
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];
83   if (mContent)
84     mMenuGroupOwner->UnregisterForContentChanges(mContent);
85   if (mCommandContent)
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;
98   mType = aItemType;
99   mMenuParent = aParent;
100   mContent = aNode;
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
111   if (doc) {
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);
122       }
123     }
124   }
126   // decide enabled state based on command content if it exists, otherwise do it based
127   // on our own content
128   PRBool isEnabled;
129   if (mCommandContent)
130     isEnabled = !mCommandContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::disabled, nsWidgetAtoms::_true, eCaseMatters);
131   else
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];
137   }
138   else {
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
148     if (doc) {
149       nsAutoString keyValue;
150       mContent->GetAttr(kNameSpaceID_None, nsWidgetAtoms::key, keyValue);
151       if (!keyValue.IsEmpty()) {
152         nsIContent *keyContent = doc->GetElementById(keyValue);
153         if (keyContent) {
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);
162         }
163       }
164     }
165   }
167   mIcon = new nsMenuItemIconX(this, mContent, mNativeMenuItem);
168   if (!mIcon)
169     return NS_ERROR_OUT_OF_MEMORY;
171   return NS_OK;
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;
181   
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);
186   
187   // update native menu item
188   if (mIsChecked)
189     [mNativeMenuItem setState:NSOnState];
190   else
191     [mNativeMenuItem setState:NSOffState];
193   return NS_OK;
195   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
198 EMenuItemType nsMenuItemX::GetMenuItemType()
200   return mType;
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 */
214   }
216   nsMenuUtilsX::DispatchCommandTo(mContent);
219 nsresult nsMenuItemX::DispatchDOMEvent(const nsString &eventName, PRBool *preventDefaultCalled)
221   if (!mContent)
222     return NS_ERROR_FAILURE;
224   // get owner document for content
225   nsCOMPtr<nsIDocument> parentDoc = mContent->GetOwnerDoc();
226   if (!parentDoc) {
227     NS_WARNING("Failed to get owner nsIDocument for menu item content");
228     return NS_ERROR_FAILURE;
229   }
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;
236   }
238   // create DOM event
239   nsCOMPtr<nsIDOMEvent> event;
240   nsresult rv = DOMEventFactory->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
241   if (NS_FAILED(rv)) {
242     NS_WARNING("Failed to create nsIDOMEvent");
243     return rv;
244   }
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);
251   // send DOM event
252   nsCOMPtr<nsIDOMEventTarget> eventTarget = do_QueryInterface(mContent);
253   rv = eventTarget->DispatchEvent(event, preventDefaultCalled);
254   if (NS_FAILED(rv)) {
255     NS_WARNING("Failed to send DOM event via nsIDOMEventTarget");
256     return rv;
257   }
259   return NS_OK;  
262 // Walk the sibling list looking for nodes with the same name and
263 // uncheck them all.
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
269     return;
270   
271   nsCOMPtr<nsIContent> parent = inCheckedContent->GetParent();
272   if (!parent)
273     return;
275   // loop over siblings
276   PRUint32 count = parent->GetChildCount();
277   for (PRUint32 i = 0; i < count; i++) {
278     nsIContent *sibling = parent->GetChildAt(i);
279     if (sibling) {      
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);
285       }
286     }    
287   }
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:@""];
301   else
302     [mNativeMenuItem setKeyEquivalent:keyEquivalent];
304   NS_OBJC_END_TRY_ABORT_BLOCK;
308 // nsChangeObserver
311 void
312 nsMenuItemX::ObserveAttributeChanged(nsIDocument *aDocument, nsIContent *aContent, nsIAtom *aAttribute)
314   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
316   if (!aContent)
317     return;
318   
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);
327       }
328       mMenuParent->SetRebuild(PR_TRUE);
329     }
330     else if (aAttribute == nsWidgetAtoms::hidden ||
331              aAttribute == nsWidgetAtoms::collapsed ||
332              aAttribute == nsWidgetAtoms::label) {
333       mMenuParent->SetRebuild(PR_TRUE);
334     }
335     else if (aAttribute == nsWidgetAtoms::image) {
336       SetupIcon();
337     }
338     else if (aAttribute == nsWidgetAtoms::disabled) {
339       if (aContent->AttrValueIs(kNameSpaceID_None, nsWidgetAtoms::disabled, nsWidgetAtoms::_true, eCaseMatters))
340         [mNativeMenuItem setEnabled:NO];
341       else
342         [mNativeMenuItem setEnabled:YES];
343     }
344   }
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);
358         else
359           mContent->SetAttr(kNameSpaceID_None, nsWidgetAtoms::disabled, commandDisabled, PR_TRUE);
360       }
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];
364       else
365         [mNativeMenuItem setEnabled:YES];
366     }
367   }
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;
377   }
379   mMenuParent->SetRebuild(PR_TRUE);
382 void nsMenuItemX::ObserveContentInserted(nsIDocument *aDocument, nsIContent *aChild, PRInt32 aIndexInContainer)
384   mMenuParent->SetRebuild(PR_TRUE);
387 void nsMenuItemX::SetupIcon()
389   if (mIcon)
390     mIcon->SetupIcon();