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