зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1757402 - Add basic native context menu support on GTK. r=stransky
I had this written somewhere in a branch, but given bug 1419151, I cleaned it up a bit and turned it into an actual patch that works. Behind a pref for now, because at the very least it needs: * Support for some testing-only functions. * Support for custom icons (for web extensions and so on). The key point is that this adds the relevant code to map a XUL menu to a GMenuModel, which we could then export via `g_dbus_connection_export_menu_model()`, which seems like a much simpler approach to support stuff like bug 1419151. See the MenuModel class in NativeMenuGtk as for how is this done. Differential Revision: https://phabricator.services.mozilla.com/D139845
This commit is contained in:
Родитель
7e4e171e27
Коммит
8724285359
|
@ -51,9 +51,7 @@
|
|||
#include "mozilla/StaticPrefs_ui.h"
|
||||
#include "mozilla/StaticPrefs_xul.h"
|
||||
#include "mozilla/widget/nsAutoRollup.h"
|
||||
#ifdef XP_MACOSX
|
||||
# include "mozilla/widget/NativeMenuSupport.h"
|
||||
#endif
|
||||
#include "mozilla/widget/NativeMenuSupport.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
@ -770,7 +768,7 @@ void nsXULPopupManager::ShowPopup(nsIContent* aPopup,
|
|||
}
|
||||
|
||||
static bool ShouldUseNativeContextMenus() {
|
||||
#ifdef XP_MACOSX
|
||||
#ifdef HAS_NATIVE_MENU_SUPPORT
|
||||
return mozilla::widget::NativeMenuSupport::ShouldUseNativeContextMenus();
|
||||
#else
|
||||
return false;
|
||||
|
@ -810,7 +808,7 @@ bool nsXULPopupManager::ShowPopupAsNativeMenu(nsIContent* aPopup, int32_t aXPos,
|
|||
}
|
||||
|
||||
RefPtr<NativeMenu> menu;
|
||||
#ifdef XP_MACOSX
|
||||
#ifdef HAS_NATIVE_MENU_SUPPORT
|
||||
if (aPopup->IsElement()) {
|
||||
menu = mozilla::widget::NativeMenuSupport::CreateNativeContextMenu(
|
||||
aPopup->AsElement());
|
||||
|
@ -849,13 +847,9 @@ bool nsXULPopupManager::ShowPopupAsNativeMenu(nsIContent* aPopup, int32_t aXPos,
|
|||
return true;
|
||||
}
|
||||
|
||||
auto scale = presContext->CSSToDevPixelScale() /
|
||||
presContext->DeviceContext()->GetDesktopToDeviceScale();
|
||||
DesktopPoint position = CSSPoint(aXPos, aYPos) * scale;
|
||||
|
||||
mNativeMenu = menu;
|
||||
mNativeMenu->AddObserver(this);
|
||||
mNativeMenu->ShowAsContextMenu(position);
|
||||
mNativeMenu->ShowAsContextMenu(presContext, CSSIntPoint(aXPos, aYPos));
|
||||
|
||||
// While the native menu is open, it consumes mouseup events.
|
||||
// Clear any :active state, mouse capture state and drag tracking now.
|
||||
|
|
|
@ -12747,6 +12747,13 @@
|
|||
value: false
|
||||
mirror: always
|
||||
|
||||
# Whether native GTK context menus are enabled.
|
||||
# Disabled because at the very least there's missing custom icon support.
|
||||
- name: widget.gtk.native-context-menus
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# Whether we use overlay scrollbars on GTK.
|
||||
- name: widget.gtk.overlay-scrollbars.enabled
|
||||
type: RelaxedAtomicBool
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "nsISupportsImpl.h"
|
||||
#include "Units.h"
|
||||
|
||||
class nsPresContext;
|
||||
|
||||
namespace mozilla {
|
||||
using Modifiers = uint16_t;
|
||||
class ErrorResult;
|
||||
|
@ -27,7 +29,8 @@ class NativeMenu {
|
|||
// Show this menu as a context menu at the specified position.
|
||||
// This call assumes that the popupshowing event for the root popup has
|
||||
// already been sent and "approved", i.e. preventDefault() was not called.
|
||||
virtual void ShowAsContextMenu(const mozilla::DesktopPoint& aPosition) = 0;
|
||||
virtual void ShowAsContextMenu(nsPresContext* aPc,
|
||||
const CSSIntPoint& aPosition) = 0;
|
||||
|
||||
// Close the menu and synchronously fire popuphiding / popuphidden events.
|
||||
// Returns false if the menu wasn't open.
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
|
||||
class nsIWidget;
|
||||
|
||||
#if defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
|
||||
# define HAS_NATIVE_MENU_SUPPORT 1
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace dom {
|
|
@ -29,7 +29,7 @@ class NativeMenuMac : public NativeMenu,
|
|||
explicit NativeMenuMac(dom::Element* aElement);
|
||||
|
||||
// NativeMenu
|
||||
void ShowAsContextMenu(const DesktopPoint& aPosition) override;
|
||||
void ShowAsContextMenu(nsPresContext*, const CSSIntPoint& aPosition) override;
|
||||
bool Close() override;
|
||||
void ActivateItem(dom::Element* aItemElement, Modifiers aModifiers, int16_t aButton,
|
||||
ErrorResult& aRv) override;
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include "PresShell.h"
|
||||
#include "nsCocoaUtils.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsPresContext.h"
|
||||
#include "nsDeviceContext.h"
|
||||
#include "nsCocoaFeatures.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -246,13 +248,17 @@ static NSAppearance* NativeAppearanceForContent(nsIContent* aContent) {
|
|||
return NSAppearanceForColorScheme(LookAndFeel::ColorSchemeForFrame(f));
|
||||
}
|
||||
|
||||
void NativeMenuMac::ShowAsContextMenu(const DesktopPoint& aPosition) {
|
||||
void NativeMenuMac::ShowAsContextMenu(nsPresContext* aPc, const CSSIntPoint& aPosition) {
|
||||
auto cssToDesktopScale =
|
||||
aPc->CSSToDevPixelScale() / aPc->DeviceContext()->GetDesktopToDeviceScale();
|
||||
const DesktopPoint desktopPoint = aPosition * cssToDesktopScale;
|
||||
|
||||
mMenu->PopupShowingEventWasSentAndApprovedExternally();
|
||||
|
||||
NSMenu* menu = mMenu->NativeNSMenu();
|
||||
NSView* view = NativeViewForContent(mMenu->Content());
|
||||
NSAppearance* appearance = NativeAppearanceForContent(mMenu->Content());
|
||||
NSPoint locationOnScreen = nsCocoaUtils::GeckoPointToCocoaPoint(aPosition);
|
||||
NSPoint locationOnScreen = nsCocoaUtils::GeckoPointToCocoaPoint(desktopPoint);
|
||||
|
||||
// Let the MOZMenuOpeningCoordinator do the actual opening, so that this ShowAsContextMenu call
|
||||
// does not spawn a nested event loop, which would be surprising to our callers.
|
||||
|
|
|
@ -30,10 +30,6 @@ EXPORTS += [
|
|||
"SDKDeclarations.h",
|
||||
]
|
||||
|
||||
EXPORTS.mozilla.widget += [
|
||||
"NativeMenuSupport.h",
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
"AppearanceOverride.mm",
|
||||
"GfxInfo.mm",
|
||||
|
|
|
@ -20,15 +20,18 @@ struct GObjectRefPtrTraits {
|
|||
static void Release(T* aObject) { g_object_unref(aObject); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct RefPtrTraits<GtkWidget> : public GObjectRefPtrTraits<GtkWidget> {};
|
||||
#define GOBJECT_TRAITS(type_) \
|
||||
template <> \
|
||||
struct RefPtrTraits<type_> : public GObjectRefPtrTraits<type_> {};
|
||||
|
||||
template <>
|
||||
struct RefPtrTraits<GDBusProxy> : public GObjectRefPtrTraits<GDBusProxy> {};
|
||||
GOBJECT_TRAITS(GtkWidget)
|
||||
GOBJECT_TRAITS(GMenu)
|
||||
GOBJECT_TRAITS(GSimpleAction)
|
||||
GOBJECT_TRAITS(GSimpleActionGroup)
|
||||
GOBJECT_TRAITS(GDBusProxy)
|
||||
GOBJECT_TRAITS(GdkDragContext)
|
||||
|
||||
template <>
|
||||
struct RefPtrTraits<GdkDragContext>
|
||||
: public GObjectRefPtrTraits<GdkDragContext> {};
|
||||
#undef GOBJECT_TRAITS
|
||||
|
||||
template <>
|
||||
struct RefPtrTraits<GVariant> {
|
||||
|
|
|
@ -0,0 +1,362 @@
|
|||
/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
|
||||
/* 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 "NativeMenuGtk.h"
|
||||
#include "mozilla/dom/Document.h"
|
||||
#include "mozilla/dom/DocumentInlines.h"
|
||||
#include "mozilla/dom/XULCommandEvent.h"
|
||||
#include "nsPresContext.h"
|
||||
#include "nsIWidget.h"
|
||||
#include "nsWindow.h"
|
||||
#include "nsStubMutationObserver.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/StaticPrefs_widget.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
namespace mozilla::widget {
|
||||
|
||||
using GtkMenuPopupAtRect = void (*)(GtkMenu* menu, GdkWindow* rect_window,
|
||||
const GdkRectangle* rect,
|
||||
GdkGravity rect_anchor,
|
||||
GdkGravity menu_anchor,
|
||||
const GdkEvent* trigger_event);
|
||||
|
||||
static bool IsDisabled(const dom::Element& aElement) {
|
||||
return aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
|
||||
nsGkAtoms::_true, eCaseMatters) ||
|
||||
aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
|
||||
nsGkAtoms::_true, eCaseMatters);
|
||||
}
|
||||
static bool NodeIsRelevant(const nsINode& aNode) {
|
||||
return aNode.IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuseparator,
|
||||
nsGkAtoms::menuitem, nsGkAtoms::menugroup);
|
||||
}
|
||||
|
||||
// If this is a radio / checkbox menuitem, get the current value.
|
||||
static Maybe<bool> GetChecked(const dom::Element& aMenuItem) {
|
||||
static dom::Element::AttrValuesArray strings[] = {nsGkAtoms::checkbox,
|
||||
nsGkAtoms::radio, nullptr};
|
||||
switch (aMenuItem.FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, strings,
|
||||
eCaseMatters)) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
return Some(aMenuItem.AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
|
||||
nsGkAtoms::_true, eCaseMatters));
|
||||
}
|
||||
|
||||
struct Actions {
|
||||
RefPtr<GSimpleActionGroup> mGroup;
|
||||
size_t mNextActionIndex = 0;
|
||||
|
||||
nsPrintfCString Register(const dom::Element&);
|
||||
void Clear();
|
||||
};
|
||||
|
||||
void ActivateItem(dom::Element* aElement) {
|
||||
RefPtr element = aElement;
|
||||
if (Maybe<bool> checked = GetChecked(*element)) {
|
||||
if (!element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
|
||||
nsGkAtoms::_false, eCaseMatters)) {
|
||||
bool newValue = !*checked;
|
||||
if (newValue) {
|
||||
element->SetAttr(kNameSpaceID_None, nsGkAtoms::checked, u"true"_ns,
|
||||
true);
|
||||
} else {
|
||||
element->UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr doc = aElement->OwnerDoc();
|
||||
RefPtr event = new dom::XULCommandEvent(doc, doc->GetPresContext(), nullptr);
|
||||
IgnoredErrorResult rv;
|
||||
event->InitCommandEvent(u"command"_ns, true, true,
|
||||
nsGlobalWindowInner::Cast(doc->GetInnerWindow()), 0,
|
||||
/* ctrlKey = */ false, /* altKey = */ false,
|
||||
/* shiftKey = */ false, /* cmdKey = */ false,
|
||||
/* button = */ MouseButton::ePrimary, nullptr, 0, rv);
|
||||
if (MOZ_UNLIKELY(rv.Failed())) {
|
||||
return;
|
||||
}
|
||||
element->DispatchEvent(*event);
|
||||
}
|
||||
|
||||
void ActivateSignal(GSimpleAction* aAction, GVariant* aParam,
|
||||
gpointer aUserData) {
|
||||
ActivateItem(static_cast<dom::Element*>(aUserData));
|
||||
}
|
||||
|
||||
nsPrintfCString Actions::Register(const dom::Element& aMenuItem) {
|
||||
nsPrintfCString actionName("item-%zu", mNextActionIndex++);
|
||||
Maybe<bool> paramValue = GetChecked(aMenuItem);
|
||||
RefPtr<GSimpleAction> action;
|
||||
if (paramValue) {
|
||||
action = dont_AddRef(g_simple_action_new_stateful(
|
||||
actionName.get(), nullptr, g_variant_new_boolean(*paramValue)));
|
||||
} else {
|
||||
action = dont_AddRef(g_simple_action_new(actionName.get(), nullptr));
|
||||
}
|
||||
g_signal_connect(action, "activate", G_CALLBACK(ActivateSignal),
|
||||
gpointer(&aMenuItem));
|
||||
g_action_map_add_action(G_ACTION_MAP(mGroup.get()), G_ACTION(action.get()));
|
||||
return actionName;
|
||||
}
|
||||
|
||||
void Actions::Clear() {
|
||||
for (size_t i = 0; i < mNextActionIndex; ++i) {
|
||||
g_action_map_remove_action(G_ACTION_MAP(mGroup.get()),
|
||||
nsPrintfCString("item-%zu", i).get());
|
||||
}
|
||||
mNextActionIndex = 0;
|
||||
}
|
||||
|
||||
class MenuModel final : public nsStubMutationObserver {
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
|
||||
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
|
||||
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
|
||||
|
||||
public:
|
||||
explicit MenuModel(dom::Element* aElement) : mElement(aElement) {
|
||||
mElement->AddMutationObserver(this);
|
||||
mGMenu = dont_AddRef(g_menu_new());
|
||||
mActions.mGroup = dont_AddRef(g_simple_action_group_new());
|
||||
}
|
||||
|
||||
GMenuModel* GetModel() { return G_MENU_MODEL(mGMenu.get()); }
|
||||
GActionGroup* GetActionGroup() {
|
||||
return G_ACTION_GROUP(mActions.mGroup.get());
|
||||
}
|
||||
|
||||
dom::Element* Element() { return mElement; }
|
||||
|
||||
void RecomputeModelIfNeeded();
|
||||
|
||||
bool IsShowing() { return mPoppedUp; }
|
||||
void WillShow() {
|
||||
mPoppedUp = true;
|
||||
RecomputeModelIfNeeded();
|
||||
}
|
||||
void DidHide() { mPoppedUp = false; }
|
||||
|
||||
private:
|
||||
virtual ~MenuModel() { mElement->RemoveMutationObserver(this); }
|
||||
|
||||
void DirtyModel() {
|
||||
mDirty = true;
|
||||
if (mPoppedUp) {
|
||||
RecomputeModelIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
RefPtr<dom::Element> mElement;
|
||||
RefPtr<GMenu> mGMenu;
|
||||
Actions mActions;
|
||||
bool mDirty = true;
|
||||
bool mPoppedUp = false;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(MenuModel, nsIMutationObserver)
|
||||
|
||||
void MenuModel::ContentRemoved(nsIContent* aChild, nsIContent*) {
|
||||
if (NodeIsRelevant(*aChild)) {
|
||||
DirtyModel();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuModel::ContentInserted(nsIContent* aChild) {
|
||||
if (NodeIsRelevant(*aChild)) {
|
||||
DirtyModel();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuModel::ContentAppended(nsIContent* aChild) {
|
||||
if (NodeIsRelevant(*aChild)) {
|
||||
DirtyModel();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuModel::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
|
||||
nsAtom* aAttribute, int32_t aModType,
|
||||
const nsAttrValue* aOldValue) {
|
||||
if (NodeIsRelevant(*aElement) &&
|
||||
(aAttribute == nsGkAtoms::label || aAttribute == nsGkAtoms::aria_label ||
|
||||
aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::hidden)) {
|
||||
DirtyModel();
|
||||
}
|
||||
}
|
||||
|
||||
static const dom::Element* GetMenuPopupChild(const dom::Element& aElement) {
|
||||
for (const nsIContent* child = aElement.GetFirstChild(); child;
|
||||
child = child->GetNextSibling()) {
|
||||
if (child->IsXULElement(nsGkAtoms::menupopup)) {
|
||||
return child->AsElement();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void RecomputeModelFor(GMenu* aMenu, Actions& aActions,
|
||||
const dom::Element& aElement) {
|
||||
RefPtr<GMenu> sectionMenu;
|
||||
auto FlushSectionMenu = [&] {
|
||||
if (sectionMenu) {
|
||||
g_menu_append_section(aMenu, nullptr, G_MENU_MODEL(sectionMenu.get()));
|
||||
sectionMenu = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
for (const nsIContent* child = aElement.GetFirstChild(); child;
|
||||
child = child->GetNextSibling()) {
|
||||
if (child->IsXULElement(nsGkAtoms::menuitem) &&
|
||||
!IsDisabled(*child->AsElement())) {
|
||||
nsAutoString label;
|
||||
child->AsElement()->GetAttr(nsGkAtoms::label, label);
|
||||
if (label.IsEmpty()) {
|
||||
child->AsElement()->GetAttr(nsGkAtoms::aria_label, label);
|
||||
}
|
||||
nsPrintfCString actionName("menu.%s",
|
||||
aActions.Register(*child->AsElement()).get());
|
||||
g_menu_append(sectionMenu ? sectionMenu.get() : aMenu,
|
||||
NS_ConvertUTF16toUTF8(label).get(), actionName.get());
|
||||
continue;
|
||||
}
|
||||
if (child->IsXULElement(nsGkAtoms::menuseparator)) {
|
||||
FlushSectionMenu();
|
||||
sectionMenu = dont_AddRef(g_menu_new());
|
||||
continue;
|
||||
}
|
||||
if (child->IsXULElement(nsGkAtoms::menugroup)) {
|
||||
FlushSectionMenu();
|
||||
sectionMenu = dont_AddRef(g_menu_new());
|
||||
RecomputeModelFor(sectionMenu, aActions, *child->AsElement());
|
||||
FlushSectionMenu();
|
||||
continue;
|
||||
}
|
||||
if (child->IsXULElement(nsGkAtoms::menu) &&
|
||||
!IsDisabled(*child->AsElement())) {
|
||||
if (const auto* popup = GetMenuPopupChild(*child->AsElement())) {
|
||||
RefPtr<GMenu> submenu = dont_AddRef(g_menu_new());
|
||||
RecomputeModelFor(submenu, aActions, *popup);
|
||||
nsAutoString label;
|
||||
child->AsElement()->GetAttr(nsGkAtoms::label, label);
|
||||
g_menu_append_submenu(sectionMenu ? sectionMenu.get() : aMenu,
|
||||
NS_ConvertUTF16toUTF8(label).get(),
|
||||
G_MENU_MODEL(submenu.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlushSectionMenu();
|
||||
}
|
||||
|
||||
void MenuModel::RecomputeModelIfNeeded() {
|
||||
if (!mDirty) {
|
||||
return;
|
||||
}
|
||||
mActions.Clear();
|
||||
g_menu_remove_all(mGMenu.get());
|
||||
RecomputeModelFor(mGMenu.get(), mActions, *mElement);
|
||||
}
|
||||
|
||||
static GtkMenuPopupAtRect GetPopupAtRectFn() {
|
||||
static GtkMenuPopupAtRect sFunc =
|
||||
(GtkMenuPopupAtRect)dlsym(RTLD_DEFAULT, "gtk_menu_popup_at_rect");
|
||||
return sFunc;
|
||||
}
|
||||
|
||||
bool NativeMenuGtk::CanUse() {
|
||||
return StaticPrefs::widget_gtk_native_context_menus() && GetPopupAtRectFn();
|
||||
}
|
||||
|
||||
#define METHOD_SIGNAL(name_) \
|
||||
void On##name_##Signal(GtkWidget* widget, gpointer user_data) { \
|
||||
return static_cast<NativeMenuGtk*>(user_data)->On##name_(); \
|
||||
}
|
||||
|
||||
METHOD_SIGNAL(Unmap);
|
||||
|
||||
#undef METHOD_SIGNAL
|
||||
|
||||
NativeMenuGtk::NativeMenuGtk(dom::Element* aElement)
|
||||
: mMenuModel(MakeRefPtr<MenuModel>(aElement)) {
|
||||
// Floating, so no need to dont_AddRef.
|
||||
mNativeMenu = gtk_menu_new_from_model(mMenuModel->GetModel());
|
||||
gtk_widget_insert_action_group(mNativeMenu.get(), "menu",
|
||||
mMenuModel->GetActionGroup());
|
||||
g_signal_connect(mNativeMenu, "unmap", G_CALLBACK(OnUnmapSignal), this);
|
||||
}
|
||||
|
||||
NativeMenuGtk::~NativeMenuGtk() {
|
||||
g_signal_handlers_disconnect_by_data(mNativeMenu, this);
|
||||
}
|
||||
|
||||
RefPtr<dom::Element> NativeMenuGtk::Element() { return mMenuModel->Element(); }
|
||||
|
||||
void NativeMenuGtk::ShowAsContextMenu(nsPresContext* aPc,
|
||||
const CSSIntPoint& aPosition) {
|
||||
if (mMenuModel->IsShowing()) {
|
||||
return;
|
||||
}
|
||||
RefPtr<nsIWidget> widget = aPc->GetRootWidget();
|
||||
if (NS_WARN_IF(!widget)) {
|
||||
// XXX Do we need to close menus here?
|
||||
return;
|
||||
}
|
||||
auto* win = static_cast<GdkWindow*>(widget->GetNativeData(NS_NATIVE_WINDOW));
|
||||
if (NS_WARN_IF(!win)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto pos = LayoutDeviceIntPoint::Round(aPosition * aPc->CSSToDevPixelScale());
|
||||
auto gdkPos = static_cast<nsWindow*>(widget.get())
|
||||
->DevicePixelsToGdkPointRoundDown(pos);
|
||||
|
||||
mMenuModel->WillShow();
|
||||
const GdkRectangle rect = {gdkPos.x, gdkPos.y, 1, 1};
|
||||
auto openFn = GetPopupAtRectFn();
|
||||
openFn(GTK_MENU(mNativeMenu.get()), win, &rect, GDK_GRAVITY_NORTH_WEST,
|
||||
GDK_GRAVITY_NORTH_WEST, nullptr);
|
||||
}
|
||||
|
||||
bool NativeMenuGtk::Close() {
|
||||
if (!mMenuModel->IsShowing()) {
|
||||
return false;
|
||||
}
|
||||
gtk_menu_popdown(GTK_MENU(mNativeMenu.get()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void NativeMenuGtk::OnUnmap() {
|
||||
mMenuModel->DidHide();
|
||||
for (NativeMenu::Observer* observer : mObservers.Clone()) {
|
||||
observer->OnNativeMenuClosed();
|
||||
}
|
||||
}
|
||||
|
||||
void NativeMenuGtk::ActivateItem(dom::Element* aItemElement, Modifiers,
|
||||
int16_t aButton, ErrorResult&) {
|
||||
// TODO: For testing only.
|
||||
}
|
||||
|
||||
void NativeMenuGtk::OpenSubmenu(dom::Element*) {
|
||||
// TODO: For testing mostly.
|
||||
}
|
||||
|
||||
void NativeMenuGtk::CloseSubmenu(dom::Element*) {
|
||||
// TODO: For testing mostly.
|
||||
}
|
||||
|
||||
} // namespace mozilla::widget
|
|
@ -0,0 +1,61 @@
|
|||
|
||||
/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_widget_NativeMenuGtk_h
|
||||
#define mozilla_widget_NativeMenuGtk_h
|
||||
|
||||
#include "mozilla/widget/NativeMenu.h"
|
||||
#include "GRefPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace dom {
|
||||
class Element;
|
||||
}
|
||||
|
||||
namespace widget {
|
||||
|
||||
class MenuModel;
|
||||
|
||||
class NativeMenuGtk : public NativeMenu {
|
||||
public:
|
||||
// Whether we can use native menu popups on GTK.
|
||||
static bool CanUse();
|
||||
|
||||
explicit NativeMenuGtk(dom::Element* aElement);
|
||||
|
||||
// NativeMenu
|
||||
void ShowAsContextMenu(nsPresContext*, const CSSIntPoint& aPosition) override;
|
||||
bool Close() override;
|
||||
void ActivateItem(dom::Element* aItemElement, Modifiers aModifiers,
|
||||
int16_t aButton, ErrorResult& aRv) override;
|
||||
void OpenSubmenu(dom::Element* aMenuElement) override;
|
||||
void CloseSubmenu(dom::Element* aMenuElement) override;
|
||||
RefPtr<dom::Element> Element() override;
|
||||
void AddObserver(NativeMenu::Observer* aObserver) override {
|
||||
mObservers.AppendElement(aObserver);
|
||||
}
|
||||
void RemoveObserver(NativeMenu::Observer* aObserver) override {
|
||||
mObservers.RemoveElement(aObserver);
|
||||
}
|
||||
|
||||
void Dump();
|
||||
|
||||
void OnUnmap();
|
||||
|
||||
protected:
|
||||
virtual ~NativeMenuGtk();
|
||||
|
||||
bool mPoppedUp = false;
|
||||
RefPtr<GtkWidget> mNativeMenu;
|
||||
RefPtr<MenuModel> mMenuModel;
|
||||
nsTArray<NativeMenu::Observer*> mObservers;
|
||||
};
|
||||
|
||||
} // namespace widget
|
||||
} // namespace mozilla
|
||||
|
||||
#endif
|
|
@ -0,0 +1,29 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/widget/NativeMenuSupport.h"
|
||||
|
||||
#include "MainThreadUtils.h"
|
||||
#include "NativeMenuGtk.h"
|
||||
|
||||
namespace mozilla::widget {
|
||||
|
||||
void NativeMenuSupport::CreateNativeMenuBar(nsIWidget* aParent,
|
||||
dom::Element* aMenuBarElement) {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread(),
|
||||
"Attempting to create native menu bar on wrong thread!");
|
||||
// TODO
|
||||
}
|
||||
|
||||
already_AddRefed<NativeMenu> NativeMenuSupport::CreateNativeContextMenu(
|
||||
dom::Element* aPopup) {
|
||||
return MakeAndAddRef<NativeMenuGtk>(aPopup);
|
||||
}
|
||||
|
||||
bool NativeMenuSupport::ShouldUseNativeContextMenus() {
|
||||
return NativeMenuGtk::CanUse();
|
||||
}
|
||||
|
||||
} // namespace mozilla::widget
|
|
@ -51,6 +51,8 @@ UNIFIED_SOURCES += [
|
|||
"MozContainer.cpp",
|
||||
"MPRISServiceHandler.cpp",
|
||||
"NativeKeyBindings.cpp",
|
||||
"NativeMenuGtk.cpp",
|
||||
"NativeMenuSupport.cpp",
|
||||
"nsApplicationChooser.cpp",
|
||||
"nsAppShell.cpp",
|
||||
"nsBidiKeyboard.cpp",
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#if defined(MOZ_WAYLAND)
|
||||
# include "nsClipboardWayland.h"
|
||||
#endif
|
||||
#include "nsGtkUtils.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsNetUtil.h"
|
||||
|
|
|
@ -189,6 +189,7 @@ EXPORTS.mozilla.widget += [
|
|||
"InProcessCompositorWidget.h",
|
||||
"MediaKeysEventSourceFactory.h",
|
||||
"NativeMenu.h",
|
||||
"NativeMenuSupport.h",
|
||||
"nsAutoRollup.h",
|
||||
"nsXPLookAndFeel.h",
|
||||
"PuppetBidiKeyboard.h",
|
||||
|
|
Загрузка…
Ссылка в новой задаче