summaryrefslogtreecommitdiffstats
path: root/widget/gtk/nsMenuBar.cpp
diff options
context:
space:
mode:
authorLootyhoof <lootyhoofer@gmail.com>2020-06-06 00:37:29 +0100
committerwolfbeast <mcwerewolf@wolfbeast.com>2020-06-13 11:49:36 +0200
commit6235023c8bff80273981c70a06cb9decb4a4ffa5 (patch)
tree9673404b3735e8a8b6bd3e890c3d6b5d2d34d76c /widget/gtk/nsMenuBar.cpp
parentf51beb3fdab3d586af081ac14478ea59e0523af4 (diff)
downloadUXP-6235023c8bff80273981c70a06cb9decb4a4ffa5.tar
UXP-6235023c8bff80273981c70a06cb9decb4a4ffa5.tar.gz
UXP-6235023c8bff80273981c70a06cb9decb4a4ffa5.tar.lz
UXP-6235023c8bff80273981c70a06cb9decb4a4ffa5.tar.xz
UXP-6235023c8bff80273981c70a06cb9decb4a4ffa5.zip
Issue MoonchildProductions/UXP#1578 - Add global menubar support for GTK
Diffstat (limited to 'widget/gtk/nsMenuBar.cpp')
-rw-r--r--widget/gtk/nsMenuBar.cpp541
1 files changed, 541 insertions, 0 deletions
diff --git a/widget/gtk/nsMenuBar.cpp b/widget/gtk/nsMenuBar.cpp
new file mode 100644
index 000000000..e7caf119c
--- /dev/null
+++ b/widget/gtk/nsMenuBar.cpp
@@ -0,0 +1,541 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Move.h"
+#include "mozilla/Preferences.h"
+#include "nsAutoPtr.h"
+#include "nsContentUtils.h"
+#include "nsIDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMEventListener.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIDOMKeyEvent.h"
+#include "nsIRunnable.h"
+#include "nsIWidget.h"
+#include "nsTArray.h"
+#include "nsUnicharUtils.h"
+
+#include "nsMenu.h"
+#include "nsNativeMenuAtoms.h"
+#include "nsNativeMenuService.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <glib.h>
+#include <glib-object.h>
+
+#include "nsMenuBar.h"
+
+using namespace mozilla;
+
+static bool
+ShouldHandleKeyEvent(nsIDOMEvent* aEvent) {
+ bool handled, trusted = false;
+ aEvent->GetPreventDefault(&handled);
+ aEvent->GetIsTrusted(&trusted);
+
+ if (handled || !trusted) {
+ return false;
+ }
+
+ return true;
+}
+
+class nsMenuBarContentInsertedEvent : public Runnable {
+public:
+ nsMenuBarContentInsertedEvent(nsMenuBar* aMenuBar,
+ nsIContent* aChild,
+ nsIContent* aPrevSibling) :
+ mWeakMenuBar(aMenuBar),
+ mChild(aChild),
+ mPrevSibling(aPrevSibling) { }
+
+ NS_IMETHODIMP Run()
+ {
+ if (!mWeakMenuBar) {
+ return NS_OK;
+ }
+
+ static_cast<nsMenuBar* >(mWeakMenuBar.get())->HandleContentInserted(mChild,
+ mPrevSibling);
+ return NS_OK;
+ }
+
+private:
+ nsWeakMenuObject mWeakMenuBar;
+
+ nsCOMPtr<nsIContent> mChild;
+ nsCOMPtr<nsIContent> mPrevSibling;
+};
+
+class nsMenuBarContentRemovedEvent : public Runnable {
+public:
+ nsMenuBarContentRemovedEvent(nsMenuBar* aMenuBar,
+ nsIContent* aChild) :
+ mWeakMenuBar(aMenuBar),
+ mChild(aChild) { }
+
+ NS_IMETHODIMP Run()
+ {
+ if (!mWeakMenuBar) {
+ return NS_OK;
+ }
+
+ static_cast<nsMenuBar* >(mWeakMenuBar.get())->HandleContentRemoved(mChild);
+ return NS_OK;
+ }
+
+private:
+ nsWeakMenuObject mWeakMenuBar;
+
+ nsCOMPtr<nsIContent> mChild;
+};
+
+class nsMenuBar::DocEventListener final : public nsIDOMEventListener {
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ DocEventListener(nsMenuBar* aOwner) : mOwner(aOwner) { };
+
+private:
+ ~DocEventListener() { };
+
+ nsMenuBar* mOwner;
+};
+
+NS_IMPL_ISUPPORTS(nsMenuBar::DocEventListener, nsIDOMEventListener)
+
+NS_IMETHODIMP
+nsMenuBar::DocEventListener::HandleEvent(nsIDOMEvent* aEvent) {
+ nsAutoString type;
+ nsresult rv = aEvent->GetType(type);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to determine event type");
+ return rv;
+ }
+
+ if (type.Equals(NS_LITERAL_STRING("focus"))) {
+ mOwner->Focus();
+ } else if (type.Equals(NS_LITERAL_STRING("blur"))) {
+ mOwner->Blur();
+ } else if (type.Equals(NS_LITERAL_STRING("keypress"))) {
+ rv = mOwner->Keypress(aEvent);
+ } else if (type.Equals(NS_LITERAL_STRING("keydown"))) {
+ rv = mOwner->KeyDown(aEvent);
+ } else if (type.Equals(NS_LITERAL_STRING("keyup"))) {
+ rv = mOwner->KeyUp(aEvent);
+ }
+
+ return rv;
+}
+
+nsMenuBar::nsMenuBar(nsIContent* aMenuBarNode) :
+ nsMenuContainer(new nsNativeMenuDocListener(aMenuBarNode), aMenuBarNode),
+ mTopLevel(nullptr),
+ mServer(nullptr),
+ mIsActive(false) {
+ MOZ_COUNT_CTOR(nsMenuBar);
+}
+
+nsresult
+nsMenuBar::Init(nsIWidget* aParent) {
+ MOZ_ASSERT(aParent);
+
+ GdkWindow* gdkWin = static_cast<GdkWindow* >(
+ aParent->GetNativeData(NS_NATIVE_WINDOW));
+ if (!gdkWin) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gpointer user_data = nullptr;
+ gdk_window_get_user_data(gdkWin, &user_data);
+ if (!user_data || !GTK_IS_CONTAINER(user_data)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mTopLevel = gtk_widget_get_toplevel(GTK_WIDGET(user_data));
+ if (!mTopLevel) {
+ return NS_ERROR_FAILURE;
+ }
+
+ g_object_ref(mTopLevel);
+
+ nsAutoCString path;
+ path.Append(NS_LITERAL_CSTRING("/com/canonical/menu/"));
+ char xid[10];
+ sprintf(xid, "%X", static_cast<uint32_t>(
+ GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel))));
+ path.Append(xid);
+
+ mServer = dbusmenu_server_new(path.get());
+ if (!mServer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CreateNativeData();
+ if (!GetNativeData()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ dbusmenu_server_set_root(mServer, GetNativeData());
+
+ mEventListener = new DocEventListener(this);
+
+ mDocument = do_QueryInterface(ContentNode()->OwnerDoc());
+
+ mAccessKey = Preferences::GetInt("ui.key.menuAccessKey");
+ if (mAccessKey == nsIDOMKeyEvent::DOM_VK_SHIFT) {
+ mAccessKeyMask = eModifierShift;
+ } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_CONTROL) {
+ mAccessKeyMask = eModifierCtrl;
+ } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_ALT) {
+ mAccessKeyMask = eModifierAlt;
+ } else if (mAccessKey == nsIDOMKeyEvent::DOM_VK_META) {
+ mAccessKeyMask = eModifierMeta;
+ } else {
+ mAccessKeyMask = eModifierAlt;
+ }
+
+ return NS_OK;
+}
+
+void
+nsMenuBar::Build() {
+ uint32_t count = ContentNode()->GetChildCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsIContent* childContent = ContentNode()->GetChildAt(i);
+
+ UniquePtr<nsMenuObject> child = CreateChild(childContent);
+
+ if (!child) {
+ continue;
+ }
+
+ AppendChild(Move(child));
+ }
+}
+
+void
+nsMenuBar::DisconnectDocumentEventListeners() {
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("focus"),
+ mEventListener,
+ true);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("blur"),
+ mEventListener,
+ true);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("keypress"),
+ mEventListener,
+ false);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("keydown"),
+ mEventListener,
+ false);
+ mDocument->RemoveEventListener(NS_LITERAL_STRING("keyup"),
+ mEventListener,
+ false);
+}
+
+void
+nsMenuBar::SetShellShowingMenuBar(bool aShowing) {
+ ContentNode()->OwnerDoc()->GetRootElement()->SetAttr(
+ kNameSpaceID_None, nsNativeMenuAtoms::shellshowingmenubar,
+ aShowing ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"),
+ true);
+}
+
+void
+nsMenuBar::Focus() {
+ ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey,
+ NS_LITERAL_STRING("false"), true);
+}
+
+void
+nsMenuBar::Blur() {
+ // We do this here in case we lose focus before getting the
+ // keyup event, which leaves the menubar state looking like
+ // the alt key is stuck down
+ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
+}
+
+nsMenuBar::ModifierFlags
+nsMenuBar::GetModifiersFromEvent(nsIDOMKeyEvent* aEvent) {
+ ModifierFlags modifiers = static_cast<ModifierFlags>(0);
+ bool modifier;
+
+ aEvent->GetAltKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierAlt);
+ }
+
+ aEvent->GetShiftKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierShift);
+ }
+
+ aEvent->GetCtrlKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierCtrl);
+ }
+
+ aEvent->GetMetaKey(&modifier);
+ if (modifier) {
+ modifiers = static_cast<ModifierFlags>(modifiers | eModifierMeta);
+ }
+
+ return modifiers;
+}
+
+nsresult
+nsMenuBar::Keypress(nsIDOMEvent* aEvent) {
+ if (!ShouldHandleKeyEvent(aEvent)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
+ if (!keyEvent) {
+ return NS_OK;
+ }
+
+ ModifierFlags modifiers = GetModifiersFromEvent(keyEvent);
+ if (((modifiers & mAccessKeyMask) == 0) ||
+ ((modifiers & ~mAccessKeyMask) != 0)) {
+ return NS_OK;
+ }
+
+ uint32_t charCode;
+ keyEvent->GetCharCode(&charCode);
+ if (charCode == 0) {
+ return NS_OK;
+ }
+
+ char16_t ch = char16_t(charCode);
+ char16_t chl = ToLowerCase(ch);
+ char16_t chu = ToUpperCase(ch);
+
+ nsMenuObject* found = nullptr;
+ uint32_t count = ChildCount();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsAutoString accesskey;
+ ChildAt(i)->ContentNode()->GetAttr(kNameSpaceID_None,
+ nsGkAtoms::accesskey,
+ accesskey);
+ const nsAutoString::char_type* key = accesskey.BeginReading();
+ if (*key == chu ||* key == chl) {
+ found = ChildAt(i);
+ break;
+ }
+ }
+
+ if (!found || found->Type() != nsMenuObject::eType_Menu) {
+ return NS_OK;
+ }
+
+ ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey,
+ NS_LITERAL_STRING("true"), true);
+ static_cast<nsMenu* >(found)->OpenMenu();
+
+ aEvent->StopPropagation();
+ aEvent->PreventDefault();
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuBar::KeyDown(nsIDOMEvent* aEvent) {
+ if (!ShouldHandleKeyEvent(aEvent)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
+ if (!keyEvent) {
+ return NS_OK;
+ }
+
+ uint32_t keyCode;
+ keyEvent->GetKeyCode(&keyCode);
+ ModifierFlags modifiers = GetModifiersFromEvent(keyEvent);
+ if ((keyCode != mAccessKey) || ((modifiers & ~mAccessKeyMask) != 0)) {
+ return NS_OK;
+ }
+
+ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NOTICE);
+
+ return NS_OK;
+}
+
+nsresult
+nsMenuBar::KeyUp(nsIDOMEvent* aEvent) {
+ if (!ShouldHandleKeyEvent(aEvent)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
+ if (!keyEvent) {
+ return NS_OK;
+ }
+
+ uint32_t keyCode;
+ keyEvent->GetKeyCode(&keyCode);
+ if (keyCode == mAccessKey) {
+ dbusmenu_server_set_status(mServer, DBUSMENU_STATUS_NORMAL);
+ }
+
+ return NS_OK;
+}
+
+void
+nsMenuBar::HandleContentInserted(nsIContent* aChild, nsIContent* aPrevSibling) {
+ UniquePtr<nsMenuObject> child = CreateChild(aChild);
+
+ if (!child) {
+ return;
+ }
+
+ InsertChildAfter(Move(child), aPrevSibling);
+}
+
+void
+nsMenuBar::HandleContentRemoved(nsIContent* aChild) {
+ RemoveChild(aChild);
+}
+
+void
+nsMenuBar::OnContentInserted(nsIContent* aContainer, nsIContent* aChild,
+ nsIContent* aPrevSibling) {
+ MOZ_ASSERT(aContainer == ContentNode(),
+ "Received an event that wasn't meant for us");
+
+ nsContentUtils::AddScriptRunner(
+ new nsMenuBarContentInsertedEvent(this, aChild, aPrevSibling));
+}
+
+void
+nsMenuBar::OnContentRemoved(nsIContent* aContainer, nsIContent* aChild) {
+ MOZ_ASSERT(aContainer == ContentNode(),
+ "Received an event that wasn't meant for us");
+
+ nsContentUtils::AddScriptRunner(
+ new nsMenuBarContentRemovedEvent(this, aChild));
+}
+
+nsMenuBar::~nsMenuBar() {
+ nsNativeMenuService* service = nsNativeMenuService::GetSingleton();
+ if (service) {
+ service->NotifyNativeMenuBarDestroyed(this);
+ }
+
+ if (ContentNode()) {
+ SetShellShowingMenuBar(false);
+ }
+
+ // We want to destroy all children before dropping our reference
+ // to the doc listener
+ while (ChildCount() > 0) {
+ RemoveChildAt(0);
+ }
+
+ if (mTopLevel) {
+ g_object_unref(mTopLevel);
+ }
+
+ if (DocListener()) {
+ DocListener()->Stop();
+ }
+
+ if (mDocument) {
+ DisconnectDocumentEventListeners();
+ }
+
+ if (mServer) {
+ g_object_unref(mServer);
+ }
+
+ MOZ_COUNT_DTOR(nsMenuBar);
+}
+
+/* static */ UniquePtr<nsMenuBar>
+nsMenuBar::Create(nsIWidget* aParent, nsIContent* aMenuBarNode) {
+ UniquePtr<nsMenuBar> menubar(new nsMenuBar(aMenuBarNode));
+ if (NS_FAILED(menubar->Init(aParent))) {
+ return nullptr;
+ }
+
+ return Move(menubar);
+}
+
+nsMenuObject::EType
+nsMenuBar::Type() const {
+ return eType_MenuBar;
+}
+
+bool
+nsMenuBar::IsBeingDisplayed() const {
+ return true;
+}
+
+uint32_t
+nsMenuBar::WindowId() const {
+ return static_cast<uint32_t>(GDK_WINDOW_XID(gtk_widget_get_window(mTopLevel)));
+}
+
+nsAdoptingCString
+nsMenuBar::ObjectPath() const {
+ gchar* tmp;
+ g_object_get(mServer, DBUSMENU_SERVER_PROP_DBUS_OBJECT, &tmp, NULL);
+ nsAdoptingCString result(tmp);
+
+ return result;
+}
+
+void
+nsMenuBar::Activate() {
+ if (mIsActive) {
+ return;
+ }
+
+ mIsActive = true;
+
+ mDocument->AddEventListener(NS_LITERAL_STRING("focus"),
+ mEventListener,
+ true);
+ mDocument->AddEventListener(NS_LITERAL_STRING("blur"),
+ mEventListener,
+ true);
+ mDocument->AddEventListener(NS_LITERAL_STRING("keypress"),
+ mEventListener,
+ false);
+ mDocument->AddEventListener(NS_LITERAL_STRING("keydown"),
+ mEventListener,
+ false);
+ mDocument->AddEventListener(NS_LITERAL_STRING("keyup"),
+ mEventListener,
+ false);
+
+ // Clear this. Not sure if we really need to though
+ ContentNode()->SetAttr(kNameSpaceID_None, nsNativeMenuAtoms::openedwithkey,
+ NS_LITERAL_STRING("false"), true);
+
+ DocListener()->Start();
+ Build();
+ SetShellShowingMenuBar(true);
+}
+
+void
+nsMenuBar::Deactivate() {
+ if (!mIsActive) {
+ return;
+ }
+
+ mIsActive = false;
+
+ SetShellShowingMenuBar(false);
+ while (ChildCount() > 0) {
+ RemoveChildAt(0);
+ }
+ DocListener()->Stop();
+ DisconnectDocumentEventListeners();
+}