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 : * Dan Rosen <dr@netscape.com>
24 : * Dean Tessman <dean_tessman@hotmail.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsMenuBarFrame.h"
41 : #include "nsIServiceManager.h"
42 : #include "nsIContent.h"
43 : #include "prtypes.h"
44 : #include "nsIAtom.h"
45 : #include "nsPresContext.h"
46 : #include "nsStyleContext.h"
47 : #include "nsCSSRendering.h"
48 : #include "nsINameSpaceManager.h"
49 : #include "nsIDocument.h"
50 : #include "nsIDOMEventTarget.h"
51 : #include "nsGkAtoms.h"
52 : #include "nsMenuFrame.h"
53 : #include "nsMenuPopupFrame.h"
54 : #include "nsGUIEvent.h"
55 : #include "nsUnicharUtils.h"
56 : #include "nsIDOMDocument.h"
57 : #include "nsPIDOMWindow.h"
58 : #include "nsIInterfaceRequestorUtils.h"
59 : #include "nsCSSFrameConstructor.h"
60 : #ifdef XP_WIN
61 : #include "nsISound.h"
62 : #include "nsWidgetsCID.h"
63 : #endif
64 : #include "nsContentUtils.h"
65 : #include "nsUTF8Utils.h"
66 :
67 :
68 : //
69 : // NS_NewMenuBarFrame
70 : //
71 : // Wrapper for creating a new menu Bar container
72 : //
73 : nsIFrame*
74 0 : NS_NewMenuBarFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
75 : {
76 0 : return new (aPresShell) nsMenuBarFrame (aPresShell, aContext);
77 : }
78 :
79 0 : NS_IMPL_FRAMEARENA_HELPERS(nsMenuBarFrame)
80 :
81 0 : NS_QUERYFRAME_HEAD(nsMenuBarFrame)
82 0 : NS_QUERYFRAME_ENTRY(nsMenuBarFrame)
83 0 : NS_QUERYFRAME_TAIL_INHERITING(nsBoxFrame)
84 :
85 : //
86 : // nsMenuBarFrame cntr
87 : //
88 0 : nsMenuBarFrame::nsMenuBarFrame(nsIPresShell* aShell, nsStyleContext* aContext):
89 : nsBoxFrame(aShell, aContext),
90 : mMenuBarListener(nsnull),
91 : mStayActive(false),
92 : mIsActive(false),
93 : mCurrentMenu(nsnull),
94 0 : mTarget(nsnull)
95 : {
96 0 : } // cntr
97 :
98 : NS_IMETHODIMP
99 0 : nsMenuBarFrame::Init(nsIContent* aContent,
100 : nsIFrame* aParent,
101 : nsIFrame* aPrevInFlow)
102 : {
103 0 : nsresult rv = nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
104 :
105 : // Create the menu bar listener.
106 0 : mMenuBarListener = new nsMenuBarListener(this);
107 0 : NS_IF_ADDREF(mMenuBarListener);
108 0 : if (! mMenuBarListener)
109 0 : return NS_ERROR_OUT_OF_MEMORY;
110 :
111 : // Hook up the menu bar as a key listener on the whole document. It will see every
112 : // key press that occurs, but after everyone else does.
113 0 : nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(aContent->GetDocument());
114 :
115 0 : mTarget = target;
116 :
117 : // Also hook up the listener to the window listening for focus events. This is so we can keep proper
118 : // state as the user alt-tabs through processes.
119 :
120 0 : target->AddEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
121 0 : target->AddEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
122 0 : target->AddEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
123 :
124 : // mousedown event should be handled in all phase
125 0 : target->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
126 0 : target->AddEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
127 0 : target->AddEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
128 :
129 0 : return rv;
130 : }
131 :
132 : NS_IMETHODIMP
133 0 : nsMenuBarFrame::SetActive(bool aActiveFlag)
134 : {
135 : // If the activity is not changed, there is nothing to do.
136 0 : if (mIsActive == aActiveFlag)
137 0 : return NS_OK;
138 :
139 0 : if (!aActiveFlag) {
140 : // Don't deactivate when switching between menus on the menubar.
141 0 : if (mStayActive)
142 0 : return NS_OK;
143 :
144 : // if there is a request to deactivate the menu bar, check to see whether
145 : // there is a menu popup open for the menu bar. In this case, don't
146 : // deactivate the menu bar.
147 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
148 0 : if (pm && pm->IsPopupOpenForMenuParent(this))
149 0 : return NS_OK;
150 : }
151 :
152 0 : mIsActive = aActiveFlag;
153 0 : if (mIsActive) {
154 0 : InstallKeyboardNavigator();
155 : }
156 : else {
157 0 : mActiveByKeyboard = false;
158 0 : RemoveKeyboardNavigator();
159 : }
160 :
161 0 : NS_NAMED_LITERAL_STRING(active, "DOMMenuBarActive");
162 0 : NS_NAMED_LITERAL_STRING(inactive, "DOMMenuBarInactive");
163 :
164 0 : FireDOMEvent(mIsActive ? active : inactive, mContent);
165 :
166 0 : return NS_OK;
167 : }
168 :
169 : nsMenuFrame*
170 0 : nsMenuBarFrame::ToggleMenuActiveState()
171 : {
172 0 : if (mIsActive) {
173 : // Deactivate the menu bar
174 0 : SetActive(false);
175 0 : if (mCurrentMenu) {
176 0 : nsMenuFrame* closeframe = mCurrentMenu;
177 0 : closeframe->SelectMenu(false);
178 0 : mCurrentMenu = nsnull;
179 0 : return closeframe;
180 : }
181 : }
182 : else {
183 : // if the menu bar is already selected (eg. mouseover), deselect it
184 0 : if (mCurrentMenu)
185 0 : mCurrentMenu->SelectMenu(false);
186 :
187 : // Activate the menu bar
188 0 : SetActive(true);
189 :
190 : // Set the active menu to be the top left item (e.g., the File menu).
191 : // We use an attribute called "menuactive" to track the current
192 : // active menu.
193 0 : nsMenuFrame* firstFrame = nsXULPopupManager::GetNextMenuItem(this, nsnull, false);
194 0 : if (firstFrame) {
195 0 : firstFrame->SelectMenu(true);
196 :
197 : // Track this item for keyboard navigation.
198 0 : mCurrentMenu = firstFrame;
199 : }
200 : }
201 :
202 0 : return nsnull;
203 : }
204 :
205 : static void
206 0 : GetInsertionPoint(nsIPresShell* aShell, nsIFrame* aFrame, nsIFrame* aChild,
207 : nsIFrame** aResult)
208 : {
209 0 : nsIContent* child = nsnull;
210 0 : if (aChild)
211 0 : child = aChild->GetContent();
212 0 : aShell->FrameConstructor()->GetInsertionPoint(aFrame, child, aResult);
213 0 : }
214 :
215 : nsMenuFrame*
216 0 : nsMenuBarFrame::FindMenuWithShortcut(nsIDOMKeyEvent* aKeyEvent)
217 : {
218 : PRUint32 charCode;
219 0 : aKeyEvent->GetCharCode(&charCode);
220 :
221 0 : nsAutoTArray<PRUint32, 10> accessKeys;
222 0 : nsEvent* nativeEvent = nsContentUtils::GetNativeEvent(aKeyEvent);
223 0 : nsKeyEvent* nativeKeyEvent = static_cast<nsKeyEvent*>(nativeEvent);
224 0 : if (nativeKeyEvent)
225 0 : nsContentUtils::GetAccessKeyCandidates(nativeKeyEvent, accessKeys);
226 0 : if (accessKeys.IsEmpty() && charCode)
227 0 : accessKeys.AppendElement(charCode);
228 :
229 0 : if (accessKeys.IsEmpty())
230 0 : return nsnull; // no character was pressed so just return
231 :
232 : // Enumerate over our list of frames.
233 0 : nsIFrame* immediateParent = nsnull;
234 0 : GetInsertionPoint(PresContext()->PresShell(), this, nsnull, &immediateParent);
235 0 : if (!immediateParent)
236 0 : immediateParent = this;
237 :
238 : // Find a most preferred accesskey which should be returned.
239 0 : nsIFrame* foundMenu = nsnull;
240 0 : PRUint32 foundIndex = accessKeys.NoIndex;
241 0 : nsIFrame* currFrame = immediateParent->GetFirstPrincipalChild();
242 :
243 0 : while (currFrame) {
244 0 : nsIContent* current = currFrame->GetContent();
245 :
246 : // See if it's a menu item.
247 0 : if (nsXULPopupManager::IsValidMenuItem(PresContext(), current, false)) {
248 : // Get the shortcut attribute.
249 0 : nsAutoString shortcutKey;
250 0 : current->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, shortcutKey);
251 0 : if (!shortcutKey.IsEmpty()) {
252 0 : ToLowerCase(shortcutKey);
253 0 : const PRUnichar* start = shortcutKey.BeginReading();
254 0 : const PRUnichar* end = shortcutKey.EndReading();
255 0 : PRUint32 ch = UTF16CharEnumerator::NextChar(&start, end);
256 0 : PRUint32 index = accessKeys.IndexOf(ch);
257 0 : if (index != accessKeys.NoIndex &&
258 : (foundIndex == accessKeys.NoIndex || index < foundIndex)) {
259 0 : foundMenu = currFrame;
260 0 : foundIndex = index;
261 : }
262 : }
263 : }
264 0 : currFrame = currFrame->GetNextSibling();
265 : }
266 0 : if (foundMenu) {
267 0 : return (foundMenu->GetType() == nsGkAtoms::menuFrame) ?
268 0 : static_cast<nsMenuFrame *>(foundMenu) : nsnull;
269 : }
270 :
271 : // didn't find a matching menu item
272 : #ifdef XP_WIN
273 : // behavior on Windows - this item is on the menu bar, beep and deactivate the menu bar
274 : if (mIsActive) {
275 : nsCOMPtr<nsISound> soundInterface = do_CreateInstance("@mozilla.org/sound;1");
276 : if (soundInterface)
277 : soundInterface->Beep();
278 : }
279 :
280 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
281 : if (pm) {
282 : nsIFrame* popup = pm->GetTopPopup(ePopupTypeAny);
283 : if (popup)
284 : pm->HidePopup(popup->GetContent(), true, true, true);
285 : }
286 :
287 : SetCurrentMenuItem(nsnull);
288 : SetActive(false);
289 :
290 : #endif // #ifdef XP_WIN
291 :
292 0 : return nsnull;
293 : }
294 :
295 : /* virtual */ nsMenuFrame*
296 0 : nsMenuBarFrame::GetCurrentMenuItem()
297 : {
298 0 : return mCurrentMenu;
299 : }
300 :
301 : NS_IMETHODIMP
302 0 : nsMenuBarFrame::SetCurrentMenuItem(nsMenuFrame* aMenuItem)
303 : {
304 0 : if (mCurrentMenu == aMenuItem)
305 0 : return NS_OK;
306 :
307 0 : if (mCurrentMenu)
308 0 : mCurrentMenu->SelectMenu(false);
309 :
310 0 : if (aMenuItem)
311 0 : aMenuItem->SelectMenu(true);
312 :
313 0 : mCurrentMenu = aMenuItem;
314 :
315 0 : return NS_OK;
316 : }
317 :
318 : void
319 0 : nsMenuBarFrame::CurrentMenuIsBeingDestroyed()
320 : {
321 0 : mCurrentMenu->SelectMenu(false);
322 0 : mCurrentMenu = nsnull;
323 0 : }
324 :
325 : class nsMenuBarSwitchMenu : public nsRunnable
326 0 : {
327 : public:
328 0 : nsMenuBarSwitchMenu(nsIContent* aMenuBar,
329 : nsIContent *aOldMenu,
330 : nsIContent *aNewMenu,
331 : bool aSelectFirstItem)
332 : : mMenuBar(aMenuBar), mOldMenu(aOldMenu), mNewMenu(aNewMenu),
333 0 : mSelectFirstItem(aSelectFirstItem)
334 : {
335 0 : }
336 :
337 0 : NS_IMETHOD Run()
338 : {
339 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
340 0 : if (!pm)
341 0 : return NS_ERROR_UNEXPECTED;
342 :
343 : // if switching from one menu to another, set a flag so that the call to
344 : // HidePopup doesn't deactivate the menubar when the first menu closes.
345 0 : nsMenuBarFrame* menubar = nsnull;
346 0 : if (mOldMenu && mNewMenu) {
347 : menubar = static_cast<nsMenuBarFrame *>
348 0 : (pm->GetFrameOfTypeForContent(mMenuBar, nsGkAtoms::menuBarFrame, false));
349 0 : if (menubar)
350 0 : menubar->SetStayActive(true);
351 : }
352 :
353 0 : if (mOldMenu) {
354 0 : nsWeakFrame weakMenuBar(menubar);
355 0 : pm->HidePopup(mOldMenu, false, false, false);
356 : // clear the flag again
357 0 : if (mNewMenu && weakMenuBar.IsAlive())
358 0 : menubar->SetStayActive(false);
359 : }
360 :
361 0 : if (mNewMenu)
362 0 : pm->ShowMenu(mNewMenu, mSelectFirstItem, false);
363 :
364 0 : return NS_OK;
365 : }
366 :
367 : private:
368 : nsCOMPtr<nsIContent> mMenuBar;
369 : nsCOMPtr<nsIContent> mOldMenu;
370 : nsCOMPtr<nsIContent> mNewMenu;
371 : bool mSelectFirstItem;
372 : };
373 :
374 : NS_IMETHODIMP
375 0 : nsMenuBarFrame::ChangeMenuItem(nsMenuFrame* aMenuItem,
376 : bool aSelectFirstItem)
377 : {
378 0 : if (mCurrentMenu == aMenuItem)
379 0 : return NS_OK;
380 :
381 : // check if there's an open context menu, we ignore this
382 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
383 0 : if (pm && pm->HasContextMenu(nsnull))
384 0 : return NS_OK;
385 :
386 0 : nsIContent* aOldMenu = nsnull, *aNewMenu = nsnull;
387 :
388 : // Unset the current child.
389 0 : bool wasOpen = false;
390 0 : if (mCurrentMenu) {
391 0 : wasOpen = mCurrentMenu->IsOpen();
392 0 : mCurrentMenu->SelectMenu(false);
393 0 : if (wasOpen) {
394 0 : nsMenuPopupFrame* popupFrame = mCurrentMenu->GetPopup();
395 0 : if (popupFrame)
396 0 : aOldMenu = popupFrame->GetContent();
397 : }
398 : }
399 :
400 : // set to null first in case the IsAlive check below returns false
401 0 : mCurrentMenu = nsnull;
402 :
403 : // Set the new child.
404 0 : if (aMenuItem) {
405 0 : nsCOMPtr<nsIContent> content = aMenuItem->GetContent();
406 0 : aMenuItem->SelectMenu(true);
407 0 : mCurrentMenu = aMenuItem;
408 0 : if (wasOpen && !aMenuItem->IsDisabled())
409 0 : aNewMenu = content;
410 : }
411 :
412 : // use an event so that hiding and showing can be done synchronously, which
413 : // avoids flickering
414 : nsCOMPtr<nsIRunnable> event =
415 0 : new nsMenuBarSwitchMenu(GetContent(), aOldMenu, aNewMenu, aSelectFirstItem);
416 0 : return NS_DispatchToCurrentThread(event);
417 : }
418 :
419 : nsMenuFrame*
420 0 : nsMenuBarFrame::Enter(nsGUIEvent* aEvent)
421 : {
422 0 : if (!mCurrentMenu)
423 0 : return nsnull;
424 :
425 0 : if (mCurrentMenu->IsOpen())
426 0 : return mCurrentMenu->Enter(aEvent);
427 :
428 0 : return mCurrentMenu;
429 : }
430 :
431 : bool
432 0 : nsMenuBarFrame::MenuClosed()
433 : {
434 0 : SetActive(false);
435 0 : if (!mIsActive && mCurrentMenu) {
436 0 : mCurrentMenu->SelectMenu(false);
437 0 : mCurrentMenu = nsnull;
438 0 : return true;
439 : }
440 0 : return false;
441 : }
442 :
443 : void
444 0 : nsMenuBarFrame::InstallKeyboardNavigator()
445 : {
446 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
447 0 : if (pm)
448 0 : pm->SetActiveMenuBar(this, true);
449 0 : }
450 :
451 : void
452 0 : nsMenuBarFrame::RemoveKeyboardNavigator()
453 : {
454 0 : if (!mIsActive) {
455 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
456 0 : if (pm)
457 0 : pm->SetActiveMenuBar(this, false);
458 : }
459 0 : }
460 :
461 : void
462 0 : nsMenuBarFrame::DestroyFrom(nsIFrame* aDestructRoot)
463 : {
464 0 : nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
465 0 : if (pm)
466 0 : pm->SetActiveMenuBar(this, false);
467 :
468 0 : mTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"), mMenuBarListener, false);
469 0 : mTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"), mMenuBarListener, false);
470 0 : mTarget->RemoveEventListener(NS_LITERAL_STRING("keyup"), mMenuBarListener, false);
471 :
472 0 : mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, true);
473 0 : mTarget->RemoveEventListener(NS_LITERAL_STRING("mousedown"), mMenuBarListener, false);
474 0 : mTarget->RemoveEventListener(NS_LITERAL_STRING("blur"), mMenuBarListener, true);
475 :
476 0 : NS_IF_RELEASE(mMenuBarListener);
477 :
478 0 : nsBoxFrame::DestroyFrom(aDestructRoot);
479 0 : }
|