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>
46
#if MOZILLA_BRANCH_VERSION < 17
47
# include <nsIDOMNSEvent.h>
49
#include <nsIPrefBranch.h>
50
#include <nsIDOMKeyEvent.h>
51
#include <nsIDOMEventListener.h>
52
#include <nsICaseConversion.h>
53
#include <nsIContent.h>
54
#include <nsIDOMDocument.h>
55
#include <nsIDOMEventTarget.h>
56
#include <nsIDOMEvent.h>
58
#include <glib-object.h>
61
#include "uGlobalMenuBar.h"
62
#include "uGlobalMenu.h"
63
#include "uGlobalMenuUtils.h"
64
#include "uGlobalMenuService.h"
65
#include "uWidgetAtoms.h"
69
#define MODIFIER_SHIFT 1
70
#define MODIFIER_CONTROL 2
71
#define MODIFIER_ALT 4
72
#define MODIFIER_META 8
74
NS_IMPL_ISUPPORTS1(uGlobalMenuBar::EventListener, nsIDOMEventListener)
77
uGlobalMenuBar::EventListener::HandleEvent(nsIDOMEvent *aEvent)
80
nsresult rv = aEvent->GetType(type);
82
NS_WARNING("Failed to determine type of event");
86
if (type.EqualsLiteral("focus")) {
88
} else if (type.EqualsLiteral("blur")) {
90
} else if (type.EqualsLiteral("keypress")) {
91
rv = mMenuBar->KeyPress(aEvent);
92
} else if (type.EqualsLiteral("keydown")) {
93
rv = mMenuBar->KeyDown(aEvent);
94
} else if (type.EqualsLiteral("keyup")) {
95
rv = mMenuBar->KeyUp(aEvent);
102
uGlobalMenuBar::MapEventCallback(GtkWidget *widget,
106
uGlobalMenuBar *menubar = static_cast<uGlobalMenuBar *>(user_data);
113
uGlobalMenuBar::Register()
116
// Registration in progress
120
mCancellable = g_cancellable_new();
122
PRUint32 xidn = (PRUint32) GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel));
123
uGlobalMenuService::RegisterGlobalMenuBar(this, mCancellable, xidn, mPath);
127
uGlobalMenuBar::IndexOf(nsIContent *aContent)
133
for (uint32_t i = 0; i < mMenuObjects.Length(); i++) {
134
if (mMenuObjects[i]->GetContent() == aContent) {
143
uGlobalMenuBar::AppendMenuObject(uGlobalMenuObject *menu)
145
gboolean res = dbusmenu_menuitem_child_append(mDbusMenuItem,
146
menu->GetDbusMenuItem());
147
return res && mMenuObjects.AppendElement(menu);
151
uGlobalMenuBar::InsertMenuObjectAfterContent(uGlobalMenuObject *menu,
152
nsIContent *aPrevSibling)
154
int32_t index = IndexOf(aPrevSibling);
155
NS_ASSERTION(index >= 0 || !aPrevSibling, "Previous sibling not found");
156
if (index < 0 && aPrevSibling) {
162
gboolean res = dbusmenu_menuitem_child_add_position(mDbusMenuItem,
163
menu->GetDbusMenuItem(),
165
return res && mMenuObjects.InsertElementAt(index, menu);
169
uGlobalMenuBar::RemoveMenuObjectForContent(nsIContent *aContent)
171
int32_t index = IndexOf(aContent);
172
NS_ASSERTION(index >= 0, "Previous sibling not found");
177
gboolean res = dbusmenu_menuitem_child_delete(mDbusMenuItem,
178
mMenuObjects[index]->GetDbusMenuItem());
179
mMenuObjects[index]->Destroy();
180
mMenuObjects.RemoveElementAt(index);
186
uGlobalMenuBar::Build()
188
PRUint32 count = mContent->GetChildCount();
190
for (PRUint32 i = 0; i < count; i++) {
191
nsIContent *menuContent = mContent->GetChildAt(i);
194
nsRefPtr<uGlobalMenuObject> newItem =
195
uGlobalMenuUtils::CreateMenuObject(this, mListener, menuContent, &failed);
198
failed = !(AppendMenuObject(newItem));
201
NS_ASSERTION(!failed, "Failed to append item to menubar");
203
// XXX: Is there anything else we should do here?
204
return NS_ERROR_FAILURE;
212
uGlobalMenuBar::Init(nsIWidget *aWindow,
213
nsIContent *aMenuBar)
215
NS_ENSURE_ARG(aWindow);
216
NS_ENSURE_ARG(aMenuBar);
220
mTopLevel = uGlobalMenuUtils::WidgetToGTKWindow(aWindow);
221
if (!GTK_IS_WINDOW(mTopLevel)) {
222
return NS_ERROR_FAILURE;
225
g_object_ref(mTopLevel);
227
mPath = NS_LITERAL_CSTRING("/com/canonical/menu/");
229
sprintf(xid, "%X", (PRUint32) GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel)));
232
mServer = dbusmenu_server_new(mPath.get());
235
dbusmenu_server_set_root(mServer, mDbusMenuItem);
237
mListener = new uGlobalMenuDocListener();
239
nsresult rv = mListener->Init(mContent);
241
NS_ERROR("Failed to initialize doc listener");
247
NS_ERROR("Failed to build menubar");
251
mEventListener = new EventListener(this);
253
mDocument = mContent->OwnerDoc();
254
NS_ASSERTION(mDocument, "Menubar has no owner document!");
256
return NS_ERROR_FAILURE;
259
nsCOMPtr<nsIDOMEventTarget> docTarget = do_QueryInterface(mDocument);
260
NS_ASSERTION(docTarget, "Document failed QI to nsIDOMEventTarget");
262
return NS_ERROR_FAILURE;
265
docTarget->AddEventListener(NS_LITERAL_STRING("focus"),
268
docTarget->AddEventListener(NS_LITERAL_STRING("blur"),
271
docTarget->AddEventListener(NS_LITERAL_STRING("keypress"),
274
docTarget->AddEventListener(NS_LITERAL_STRING("keydown"),
277
docTarget->AddEventListener(NS_LITERAL_STRING("keyup"),
281
nsIPrefBranch *prefs = uGlobalMenuService::GetPrefService();
282
NS_ASSERTION(prefs, "Failed to get pref service");
284
return NS_ERROR_FAILURE;
287
prefs->GetIntPref("ui.key.menuAccessKey", &mAccessKey);
288
if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) {
289
mAccessKeyMask = MODIFIER_SHIFT;
290
} else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) {
291
mAccessKeyMask = MODIFIER_CONTROL;
292
} else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) {
293
mAccessKeyMask = MODIFIER_ALT;
294
} else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) {
295
mAccessKeyMask = MODIFIER_META;
297
mAccessKeyMask = MODIFIER_ALT;
300
/* Do this before registering for content changes, so that we
301
* don't handle the subsequent event */
302
nsCOMPtr<nsIDOMElement> self = do_QueryInterface(mContent);
303
NS_ASSERTION(self, "Content failed QI to nsIDOMElement");
305
return NS_ERROR_FAILURE;
308
self->SetAttribute(NS_LITERAL_STRING("openedwithkey"),
309
NS_LITERAL_STRING("false"));
311
mListener->RegisterForContentChanges(mContent, this);
313
// Unity forgets our window if it is unmapped by the application, which
314
// happens with some extensions that add "minimize to tray" type
315
// functionality. We hook on to the MapNotify event to re-register our menu
317
g_signal_connect(G_OBJECT(mTopLevel), "map-event",
318
G_CALLBACK(MapEventCallback), this);
320
if (gtk_widget_get_mapped(mTopLevel)) {
324
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
325
NS_ASSERTION(domDoc, "Document failed QI to nsIDOMDocument");
327
return NS_ERROR_FAILURE;
330
nsCOMPtr<nsIDOMElement> windowElem;
331
domDoc->GetDocumentElement(getter_AddRefs(windowElem));
332
NS_ASSERTION(windowElem, "Document has no documentElement");
334
return NS_ERROR_FAILURE;
337
return windowElem->SetAttribute(NS_LITERAL_STRING("shellshowingmenubar"),
338
NS_LITERAL_STRING("true"));
342
uGlobalMenuBar::GetModifiersFromEvent(nsIDOMKeyEvent *aKeyEvent)
344
PRUint32 modifiers = 0;
347
aKeyEvent->GetAltKey(&modifier);
349
modifiers |= MODIFIER_ALT;
352
aKeyEvent->GetShiftKey(&modifier);
354
modifiers |= MODIFIER_SHIFT;
357
aKeyEvent->GetCtrlKey(&modifier);
359
modifiers |= MODIFIER_CONTROL;
362
aKeyEvent->GetMetaKey(&modifier);
364
modifiers |= MODIFIER_META;
370
uGlobalMenuBar::uGlobalMenuBar():
371
uGlobalMenuObject(), mServer(nullptr), mTopLevel(nullptr),
372
mCancellable(nullptr)
374
MOZ_COUNT_CTOR(uGlobalMenuBar);
377
uGlobalMenuBar::~uGlobalMenuBar()
381
if (!IsDestroyed()) {
385
MOZ_COUNT_DTOR(uGlobalMenuBar);
388
/*static*/ uGlobalMenuBar*
389
uGlobalMenuBar::Create(nsIWidget *aWindow,
390
nsIContent *aMenuBar)
394
uGlobalMenuBar *menubar = new uGlobalMenuBar();
399
if (NS_FAILED(menubar->Init(aWindow, aMenuBar))) {
408
uGlobalMenuBar::Destroy()
412
NS_ASSERTION(!IsDestroyed(), "Menubar is already destroyed");
418
g_signal_handlers_disconnect_by_func(mTopLevel,
419
uGlobalMenuUtils::FuncToVoidPtr(MapEventCallback),
424
g_cancellable_cancel(mCancellable);
425
g_object_unref(mCancellable);
426
mCancellable = nullptr;
430
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
432
nsCOMPtr<nsIDOMElement> windowElem;
433
domDoc->GetDocumentElement(getter_AddRefs(windowElem));
435
windowElem->SetAttribute(NS_LITERAL_STRING("shellshowingmenubar"),
436
NS_LITERAL_STRING("false"));
440
nsCOMPtr<nsIDOMEventTarget> docTarget = do_QueryInterface(mDocument);
442
docTarget->RemoveEventListener(NS_LITERAL_STRING("focus"),
445
docTarget->RemoveEventListener(NS_LITERAL_STRING("blur"),
448
docTarget->RemoveEventListener(NS_LITERAL_STRING("keypress"),
451
docTarget->RemoveEventListener(NS_LITERAL_STRING("keydown"),
454
docTarget->RemoveEventListener(NS_LITERAL_STRING("keyup"),
461
g_object_unref(mTopLevel);
466
g_object_unref(mServer);
471
mListener->Destroy();
474
uGlobalMenuObject::Destroy();
478
uGlobalMenuBar::Blur()
480
dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
484
uGlobalMenuBar::Focus()
486
nsCOMPtr<nsIDOMElement> self = do_QueryInterface(mContent);
487
#if DEBUG_GLOBALMENU >= 2
489
self->GetAttribute(NS_LITERAL_STRING("openedwithkey"), old);
490
if (old.Equals(NS_LITERAL_STRING("true"))) {
491
LOG("Received focus - unsetting \"openedwithkey\"");
494
self->SetAttribute(NS_LITERAL_STRING("openedwithkey"),
495
NS_LITERAL_STRING("false"));
499
uGlobalMenuBar::ShouldHandleKeyEvent(nsIDOMEvent *aKeyEvent)
501
#if MOZILLA_BRANCH_VERSION < 17
502
nsCOMPtr<nsIDOMNSEvent> event = do_QueryInterface(aKeyEvent);
507
nsIDOMEvent *event = aKeyEvent;
510
bool handled, trusted;
511
event->GetPreventDefault(&handled);
512
event->GetIsTrusted(&trusted);
514
if (handled || !trusted) {
522
uGlobalMenuBar::KeyDown(nsIDOMEvent *aKeyEvent)
524
if (!ShouldHandleKeyEvent(aKeyEvent)) {
528
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
531
keyEvent->GetKeyCode(&keyCode);
532
PRUint32 modifiers = GetModifiersFromEvent(keyEvent);
533
if ((keyCode == static_cast<PRUint32>(mAccessKey)) &&
534
((modifiers & ~mAccessKeyMask) == 0)) {
535
dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NOTICE);
543
uGlobalMenuBar::KeyUp(nsIDOMEvent *aKeyEvent)
545
if (!ShouldHandleKeyEvent(aKeyEvent)) {
549
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
552
keyEvent->GetKeyCode(&keyCode);
553
if (keyCode == static_cast<PRUint32>(mAccessKey)) {
554
dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
562
uGlobalMenuBar::KeyPress(nsIDOMEvent *aKeyEvent)
564
if (!ShouldHandleKeyEvent(aKeyEvent)) {
568
nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aKeyEvent);
569
uGlobalMenuObject *found = nullptr;
573
keyEvent->GetKeyCode(&keyCode);
574
PRUint32 count = mMenuObjects.Length();
575
PRUint32 modifiers = GetModifiersFromEvent(keyEvent);
576
if ((modifiers & mAccessKeyMask) && ((modifiers & ~mAccessKeyMask) == 0)) {
577
// The menu access modifier is pressed
579
keyEvent->GetCharCode(&charCode);
581
PRUnichar ch = PRUnichar(charCode);
583
nsICaseConversion *converter= uGlobalMenuService::GetCaseConverter();
588
converter->ToUpper(ch, &chu);
589
converter->ToLower(ch, &chl);
591
NS_WARNING("No case converter");
596
for (PRUint32 i = 0; i < count; i++) {
597
nsIContent *content = mMenuObjects[i]->GetContent();
599
nsAutoString accessKey;
600
content->GetAttr(kNameSpaceID_None, uWidgetAtoms::accesskey,
602
const PRUnichar *key = accessKey.BeginReading();
603
if (*key == chl || *key == chu) {
604
found = mMenuObjects[i];
610
} else if (keyCode == nsIDOMKeyEvent::DOM_VK_F10) {
611
// Go through each mMenuObject, and find the first one
612
// that is both visible and sensitive, and mark it found
614
for (PRUint32 i = 0; i < count; i++) {
615
uGlobalMenu *menu = static_cast<uGlobalMenu *>((uGlobalMenuObject *)mMenuObjects[i]);
616
if (menu->CanOpen()) {
617
found = mMenuObjects[i];
625
nsCOMPtr<nsIDOMElement> self = do_QueryInterface(mContent);
626
self->SetAttribute(NS_LITERAL_STRING("openedwithkey"),
627
NS_LITERAL_STRING("true"));
628
uGlobalMenu *menu = static_cast<uGlobalMenu *>(found);
629
menu->OpenMenuDelayed();
630
aKeyEvent->StopPropagation();
631
aKeyEvent->PreventDefault();
638
uGlobalMenuBar::NotifyMenuBarRegistered()
640
g_object_unref(mCancellable);
641
mCancellable = nullptr;
643
SetFlags(UNITY_MENUBAR_IS_REGISTERED);
647
uGlobalMenuBar::ObserveAttributeChanged(nsIContent *aContent,
654
uGlobalMenuBar::ObserveContentRemoved(nsIContent *aContainer,
658
NS_ASSERTION(aContainer == mContent,
659
"Received an event that wasn't meant for us!");
661
RemoveMenuObjectForContent(aChild);
665
uGlobalMenuBar::ObserveContentInserted(nsIContent *aContainer,
667
nsIContent *aPrevSibling)
670
NS_ASSERTION(aContainer == mContent,
671
"Received an event that wasn't meant for us!");
673
aPrevSibling = uGlobalMenuUtils::GetPreviousSupportedSibling(aPrevSibling);
676
nsRefPtr<uGlobalMenuObject> newItem =
677
uGlobalMenuUtils::CreateMenuObject(this, mListener, aChild, &failed);
680
failed = !(InsertMenuObjectAfterContent(newItem, aPrevSibling));
683
NS_ASSERTION(!failed, "Failed to insert item in to menubar");