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 globalmenu-extension.
17
* The Initial Developer of the Original Code is
19
* Portions created by the Initial Developer are Copyright (C) 2010
20
* the Initial Developer. All Rights Reserved.
23
* Chris Coulson <chris.coulson@canonical.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 ***** */
40
#include <nsIObserver.h>
41
#include <nsIWidget.h>
42
#include <nsIDOMWindow.h>
43
#include <nsIXULWindow.h>
44
#include <nsIInterfaceRequestorUtils.h>
45
#include <nsIDocShell.h>
47
# include <nsIDOMNSEvent.h>
49
#include <nsIPrefBranch.h>
50
#include <nsIDOMKeyEvent.h>
51
#include <nsIDOMEventListener.h>
52
#include <nsICaseConversion.h>
54
#include <glib-object.h>
58
#include "uGlobalMenuBar.h"
59
#include "uGlobalMenu.h"
60
#include "uGlobalMenuUtils.h"
61
#include "uGlobalMenuService.h"
62
#include "uWidgetAtoms.h"
66
#define MODIFIER_SHIFT 1
67
#define MODIFIER_CONTROL 2
68
#define MODIFIER_ALT 4
69
#define MODIFIER_META 8
71
NS_IMPL_ISUPPORTS1(uGlobalMenuBarListener, nsIDOMEventListener)
74
uGlobalMenuBarListener::HandleEvent(nsIDOMEvent *aEvent)
77
nsresult rv = aEvent->GetType(type);
79
NS_WARNING("Failed to determine type of event");
83
if (type.EqualsLiteral("focus")) {
85
} else if (type.EqualsLiteral("blur")) {
87
} else if (type.EqualsLiteral("keypress")) {
88
rv = mMenuBar->KeyPress(aEvent);
89
} else if (type.EqualsLiteral("keydown")) {
90
rv = mMenuBar->KeyDown(aEvent);
91
} else if (type.EqualsLiteral("keyup")) {
92
rv = mMenuBar->KeyUp(aEvent);
99
uGlobalMenuBar::WidgetToGTKWindow(nsIWidget *aWidget)
101
// Get the main GDK drawing window from our nsIWidget
102
GdkWindow *window = static_cast<GdkWindow *>(aWidget->GetNativeData(NS_NATIVE_WINDOW));
106
// Get the widget for the main drawing window, which should be a MozContainer
107
gpointer user_data = nsnull;
108
gdk_window_get_user_data(window, &user_data);
109
if (!user_data || !GTK_IS_CONTAINER(user_data))
112
return gtk_widget_get_toplevel(GTK_WIDGET(user_data));
116
uGlobalMenuBar::AppendMenuObject(uGlobalMenuObject *menu)
118
gboolean res = dbusmenu_menuitem_child_append(mDbusMenuItem,
119
menu->GetDbusMenuItem());
120
return res && mMenuObjects.AppendElement(menu);
124
uGlobalMenuBar::InsertMenuObjectAt(uGlobalMenuObject *menu,
127
gboolean res = dbusmenu_menuitem_child_add_position(mDbusMenuItem,
128
menu->GetDbusMenuItem(),
130
return res && mMenuObjects.InsertElementAt(index, menu);
134
uGlobalMenuBar::RemoveMenuObjectAt(PRUint32 index)
136
NS_ASSERTION(index < mMenuObjects.Length(), "Invalid index");
137
if (index >= mMenuObjects.Length()) {
141
gboolean res = dbusmenu_menuitem_child_delete(mDbusMenuItem,
142
mMenuObjects[index]->GetDbusMenuItem());
143
mMenuObjects.RemoveElementAt(index);
149
uGlobalMenuBar::Build()
151
PRUint32 count = mContent->GetChildCount();
153
for (PRUint32 i = 0; i < count; i++) {
154
nsIContent *menuContent = mContent->GetChildAt(i);
155
uGlobalMenuObject *newItem =
156
NewGlobalMenuItem(static_cast<uGlobalMenuObject *>(this),
157
mListener, menuContent, this);
160
res = AppendMenuObject(newItem);
162
NS_ASSERTION(res, "Failed to append menuitem. Our menu representation is out-of-sync with reality");
164
// XXX: Is there anything else we should do here?
165
return NS_ERROR_FAILURE;
173
uGlobalMenuBar::InitializeDbusMenuItem()
175
if (!mDbusMenuItem) {
176
mDbusMenuItem = dbusmenu_menuitem_new();
181
uGlobalMenuBar::Init(nsIWidget *aWindow,
182
nsIContent *aMenuBar)
184
NS_ENSURE_ARG(aWindow);
185
NS_ENSURE_ARG(aMenuBar);
187
// We create this early so that IsRegistered() works
188
mCancellable = uGlobalMenuRequestAutoCanceller::Create();
192
mTopLevel = WidgetToGTKWindow(aWindow);
193
if (!GTK_IS_WINDOW(mTopLevel)) {
194
return NS_ERROR_FAILURE;
197
g_object_ref(mTopLevel);
199
nsCAutoString path = NS_LITERAL_CSTRING("/com/canonical/menu/");
201
sprintf(xid, "%X", (PRUint32) GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel)));
204
mServer = dbusmenu_server_new(path.get());
206
NS_WARNING("Failed to create DbusmenuServer");
207
return NS_ERROR_OUT_OF_MEMORY;
210
InitializeDbusMenuItem();
212
if (!mDbusMenuItem) {
213
NS_WARNING("Failed to create DbusmenuMenuitem");
214
return NS_ERROR_OUT_OF_MEMORY;
217
dbusmenu_server_set_root(mServer, mDbusMenuItem);
219
mListener = new uGlobalMenuDocListener();
221
nsresult rv = mListener->Init(mContent);
223
NS_WARNING("Failed to initialize doc listener");
229
NS_WARNING("Failed to build menubar");
233
mEventListener = new uGlobalMenuBarListener(this);
235
mDocTarget = do_QueryInterface(mContent->GetCurrentDoc());
237
mDocTarget->AddEventListener(NS_LITERAL_STRING("focus"),
240
mDocTarget->AddEventListener(NS_LITERAL_STRING("blur"),
243
mDocTarget->AddEventListener(NS_LITERAL_STRING("keypress"),
246
mDocTarget->AddEventListener(NS_LITERAL_STRING("keydown"),
249
mDocTarget->AddEventListener(NS_LITERAL_STRING("keyup"),
253
nsIPrefBranch *prefs = uGlobalMenuService::GetPrefService();
255
return NS_ERROR_FAILURE;
258
prefs->GetIntPref("ui.key.menuAccessKey", &mAccessKey);
259
if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) {
260
mAccessKeyMask = MODIFIER_SHIFT;
261
} else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) {
262
mAccessKeyMask = MODIFIER_CONTROL;
263
} else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) {
264
mAccessKeyMask = MODIFIER_ALT;
265
} else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) {
266
mAccessKeyMask = MODIFIER_META;
268
mAccessKeyMask = MODIFIER_ALT;
271
rv = mListener->RegisterFallbackListener(this);
276
PRUint32 xidn = (PRUint32) GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel));
277
uGlobalMenuService::RegisterGlobalMenuBar(this, mCancellable, xidn, path);
284
uGlobalMenuBar::ShouldParentStayVisible(nsIContent *aContent)
286
static nsIAtom *blacklist[] =
287
{ uWidgetAtoms::toolbarspring, nsnull };
289
nsIContent *parent = aContent->GetParent();
294
PRUint32 count = parent->GetChildCount();
301
for (PRUint32 i = 0 ; i < count ; i++) {
302
nsIContent *child = parent->GetChildAt(i);
303
if (child == aContent) {
308
for (PRUint32 j = 0 ; blacklist[j] != nsnull ; j++) {
309
if (child->Tag() == blacklist[j]) {
324
uGlobalMenuBar::GetModifiersFromEvent(nsIDOMKeyEvent *aKeyEvent)
326
PRUint32 modifiers = 0;
329
aKeyEvent->GetAltKey(&modifier);
331
modifiers |= MODIFIER_ALT;
334
aKeyEvent->GetShiftKey(&modifier);
336
modifiers |= MODIFIER_SHIFT;
339
aKeyEvent->GetCtrlKey(&modifier);
341
modifiers |= MODIFIER_CONTROL;
344
aKeyEvent->GetMetaKey(&modifier);
346
modifiers |= MODIFIER_META;
353
uGlobalMenuBar::IsParentOfMenuBar(nsIContent *aContent)
355
nsIContent *tmp = mContent->GetParent();
358
if (tmp == aContent) {
362
tmp = tmp->GetParent();
369
uGlobalMenuBar::HideXULMenuBar()
371
if (mHiddenElement) {
372
mHiddenElement->SetAttr(kNameSpaceID_None, uWidgetAtoms::hidden,
373
mRestoreHidden ? NS_LITERAL_STRING("true") :
374
NS_LITERAL_STRING("false"), true);
377
nsIContent *tmp = mContent;
379
// Walk up the DOM tree until we find a node with siblings
381
if (ShouldParentStayVisible(tmp)) {
385
tmp = tmp->GetParent();
388
mHiddenElement = tmp;
389
mRestoreHidden = mHiddenElement->AttrValueIs(kNameSpaceID_None,
390
uWidgetAtoms::hidden,
394
mHiddenElement->SetAttr(kNameSpaceID_None, uWidgetAtoms::hidden,
395
NS_LITERAL_STRING("true"), true);
399
uGlobalMenuBar::ShowXULMenuBar()
401
if (mHiddenElement) {
402
mHiddenElement->SetAttr(kNameSpaceID_None, uWidgetAtoms::hidden,
403
mRestoreHidden ? NS_LITERAL_STRING("true") :
404
NS_LITERAL_STRING("false"), true);
405
mHiddenElement = nsnull;
409
uGlobalMenuBar::uGlobalMenuBar():
410
uGlobalMenuObject(eMenuBar), mServer(nsnull), mTopLevel(nsnull),
411
mOpenedByKeyboard(false)
413
MOZ_COUNT_CTOR(uGlobalMenuBar);
416
uGlobalMenuBar::~uGlobalMenuBar()
421
mDocTarget->RemoveEventListener(NS_LITERAL_STRING("focus"),
424
mDocTarget->RemoveEventListener(NS_LITERAL_STRING("blur"),
427
mDocTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"),
430
mDocTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"),
433
mDocTarget->RemoveEventListener(NS_LITERAL_STRING("keyup"),
439
mListener->UnregisterFallbackListener(this);
440
mListener->Destroy();
444
g_object_unref(mTopLevel);
447
g_object_unref(mDbusMenuItem);
450
g_object_unref(mServer);
452
MOZ_COUNT_DTOR(uGlobalMenuBar);
455
/*static*/ uGlobalMenuBar*
456
uGlobalMenuBar::Create(nsIWidget *aWindow,
457
nsIContent *aMenuBar)
459
uGlobalMenuBar *menubar = new uGlobalMenuBar();
464
if (NS_FAILED(menubar->Init(aWindow, aMenuBar))) {
473
uGlobalMenuBar::Blur()
475
dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
479
uGlobalMenuBar::Focus()
481
mOpenedByKeyboard = false;
485
uGlobalMenuBar::ShouldHandleKeyEvent(nsIDOMEvent *aKeyEvent)
488
# define nsEvent aKeyEvent
490
nsCOMPtr<nsIDOMNSEvent> nsEvent = do_QueryInterface(aKeyEvent);
496
bool handled, trusted;
497
nsEvent->GetPreventDefault(&handled);
498
nsEvent->GetIsTrusted(&trusted);
504
if (handled || !trusted) {
512
uGlobalMenuBar::KeyDown(nsIDOMEvent *aKeyEvent)
514
if (!ShouldHandleKeyEvent(aKeyEvent)) {
518
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
521
keyEvent->GetKeyCode(&keyCode);
522
PRUint32 modifiers = GetModifiersFromEvent(keyEvent);
523
if ((keyCode == static_cast<PRUint32>(mAccessKey)) &&
524
((modifiers & ~mAccessKeyMask) == 0)) {
525
dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NOTICE);
533
uGlobalMenuBar::KeyUp(nsIDOMEvent *aKeyEvent)
535
if (!ShouldHandleKeyEvent(aKeyEvent)) {
539
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
542
keyEvent->GetKeyCode(&keyCode);
543
if (keyCode == static_cast<PRUint32>(mAccessKey)) {
544
dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
552
uGlobalMenuBar::KeyPress(nsIDOMEvent *aKeyEvent)
554
if (!ShouldHandleKeyEvent(aKeyEvent)) {
558
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
559
uGlobalMenuObject *found = nsnull;
560
PRUint32 keyCode = nsnull;
563
keyEvent->GetKeyCode(&keyCode);
564
PRUint32 count = mMenuObjects.Length();
565
PRUint32 modifiers = GetModifiersFromEvent(keyEvent);
566
if ((modifiers & mAccessKeyMask) && ((modifiers & ~mAccessKeyMask) == 0)) {
567
// The menu access modifier is pressed
569
keyEvent->GetCharCode(&charCode);
571
PRUnichar ch = PRUnichar(charCode);
573
nsICaseConversion *converter= uGlobalMenuService::GetCaseConverter();
578
converter->ToUpper(ch, &chu);
579
converter->ToLower(ch, &chl);
581
NS_WARNING("No case converter");
586
for (PRUint32 i = 0; i < count; i++) {
587
nsIContent *content = mMenuObjects[i]->GetContent();
589
nsAutoString accessKey;
590
content->GetAttr(kNameSpaceID_None, uWidgetAtoms::accesskey,
592
const PRUnichar *key = accessKey.BeginReading();
593
if (*key == chl || *key == chu) {
594
found = mMenuObjects[i];
600
} else if (keyCode == nsIDOMKeyEvent::DOM_VK_F10) {
601
// Go through each mMenuObject, and find the first one
602
// that is both visible and sensitive, and mark it found
604
for (PRUint32 i = 0; i < count; i++) {
605
uGlobalMenu *menu = static_cast<uGlobalMenu *>((uGlobalMenuObject *)mMenuObjects[i]);
606
if (menu->CanOpen()) {
607
found = mMenuObjects[i];
615
mOpenedByKeyboard = true;
616
uGlobalMenu *menu = static_cast<uGlobalMenu *>(found);
618
aKeyEvent->StopPropagation();
619
aKeyEvent->PreventDefault();
626
uGlobalMenuBar::NotifyMenuBarRegistered()
629
mCancellable->Destroy();
630
mCancellable = nsnull;
633
SetFlags(UNITY_MENUBAR_IS_REGISTERED);
637
uGlobalMenuBar::WidgetHasSameToplevelWindow(nsIWidget *aWidget)
639
GtkWidget *topLevel = WidgetToGTKWindow(aWidget);
640
return GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel)) == GDK_WINDOW_XID(gtk_widget_get_window(topLevel));
644
uGlobalMenuBar::ObserveAttributeChanged(nsIDocument *aDocument,
645
nsIContent *aContent,
652
uGlobalMenuBar::ObserveContentRemoved(nsIDocument *aDocument,
653
nsIContent *aContainer,
655
PRInt32 aIndexInContainer)
657
if (IsParentOfMenuBar(aContainer)) {
662
if (aContainer != mContent) {
666
bool res = RemoveMenuObjectAt(aIndexInContainer);
667
NS_ASSERTION(res, "Failed to remove menuitem. Our menu representation is out-of-sync with reality");
668
// XXX: Is there anything else we can do if removal fails?
672
uGlobalMenuBar::ObserveContentInserted(nsIDocument *aDocument,
673
nsIContent *aContainer,
675
PRInt32 aIndexInContainer)
677
if (IsParentOfMenuBar(aContainer)) {
682
if (aContainer != mContent) {
686
uGlobalMenuObject *newItem =
687
NewGlobalMenuItem(static_cast<uGlobalMenuObject *>(this),
688
mListener, aChild, this);
691
res = InsertMenuObjectAt(newItem, aIndexInContainer);
693
NS_ASSERTION(res, "Failed to insert menuitem. Our menu representation is out-of-sync with reality");
694
// XXX: Is there anything else we can do if insertion fails?