build(deps): vendor JUCE 7.0.12
This commit is contained in:
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED
|
||||
class AccessibilityHandler::AccessibilityNativeImpl
|
||||
{
|
||||
public:
|
||||
AccessibilityNativeImpl (AccessibilityHandler&) {}
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+658
@@ -0,0 +1,658 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
int AccessibilityNativeHandle::idCounter = 0;
|
||||
|
||||
//==============================================================================
|
||||
class UIAScrollProvider final : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::IScrollProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
JUCE_COMCALL Scroll (ComTypes::ScrollAmount, ComTypes::ScrollAmount) override { return E_FAIL; }
|
||||
JUCE_COMCALL SetScrollPercent (double, double) override { return E_FAIL; }
|
||||
JUCE_COMCALL get_HorizontalScrollPercent (double*) override { return E_FAIL; }
|
||||
JUCE_COMCALL get_VerticalScrollPercent (double*) override { return E_FAIL; }
|
||||
JUCE_COMCALL get_HorizontalViewSize (double*) override { return E_FAIL; }
|
||||
JUCE_COMCALL get_VerticalViewSize (double*) override { return E_FAIL; }
|
||||
JUCE_COMCALL get_HorizontallyScrollable (BOOL*) override { return E_FAIL; }
|
||||
JUCE_COMCALL get_VerticallyScrollable (BOOL*) override { return E_FAIL; }
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAScrollProvider)
|
||||
};
|
||||
|
||||
class UIAScrollItemProvider final : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::IScrollItemProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
JUCE_COMCALL ScrollIntoView() override
|
||||
{
|
||||
if (auto* handler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&getHandler(), &AccessibilityHandler::getTableInterface))
|
||||
{
|
||||
if (auto* tableInterface = handler->getTableInterface())
|
||||
{
|
||||
tableInterface->showCell (getHandler());
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAScrollItemProvider)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static String getAutomationId (const AccessibilityHandler& handler)
|
||||
{
|
||||
auto result = handler.getTitle();
|
||||
auto* parentComponent = handler.getComponent().getParentComponent();
|
||||
|
||||
while (parentComponent != nullptr)
|
||||
{
|
||||
if (auto* parentHandler = parentComponent->getAccessibilityHandler())
|
||||
{
|
||||
auto parentTitle = parentHandler->getTitle();
|
||||
result << "." << (parentTitle.isNotEmpty() ? parentTitle : "<empty>");
|
||||
}
|
||||
|
||||
parentComponent = parentComponent->getParentComponent();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static auto roleToControlTypeId (AccessibilityRole roleType)
|
||||
{
|
||||
using namespace ComTypes::Constants;
|
||||
|
||||
switch (roleType)
|
||||
{
|
||||
case AccessibilityRole::popupMenu:
|
||||
case AccessibilityRole::dialogWindow:
|
||||
case AccessibilityRole::splashScreen:
|
||||
case AccessibilityRole::window: return UIA_WindowControlTypeId;
|
||||
|
||||
case AccessibilityRole::label:
|
||||
case AccessibilityRole::staticText: return UIA_TextControlTypeId;
|
||||
|
||||
case AccessibilityRole::column:
|
||||
case AccessibilityRole::row: return UIA_ListItemControlTypeId;
|
||||
|
||||
case AccessibilityRole::button: return UIA_ButtonControlTypeId;
|
||||
case AccessibilityRole::toggleButton: return UIA_CheckBoxControlTypeId;
|
||||
case AccessibilityRole::radioButton: return UIA_RadioButtonControlTypeId;
|
||||
case AccessibilityRole::comboBox: return UIA_ComboBoxControlTypeId;
|
||||
case AccessibilityRole::image: return UIA_ImageControlTypeId;
|
||||
case AccessibilityRole::slider: return UIA_SliderControlTypeId;
|
||||
case AccessibilityRole::editableText: return UIA_EditControlTypeId;
|
||||
case AccessibilityRole::menuItem: return UIA_MenuItemControlTypeId;
|
||||
case AccessibilityRole::menuBar: return UIA_MenuBarControlTypeId;
|
||||
case AccessibilityRole::table: return UIA_TableControlTypeId;
|
||||
case AccessibilityRole::tableHeader: return UIA_HeaderControlTypeId;
|
||||
case AccessibilityRole::cell: return UIA_DataItemControlTypeId;
|
||||
case AccessibilityRole::hyperlink: return UIA_HyperlinkControlTypeId;
|
||||
case AccessibilityRole::list: return UIA_ListControlTypeId;
|
||||
case AccessibilityRole::listItem: return UIA_ListItemControlTypeId;
|
||||
case AccessibilityRole::tree: return UIA_TreeControlTypeId;
|
||||
case AccessibilityRole::treeItem: return UIA_TreeItemControlTypeId;
|
||||
case AccessibilityRole::progressBar: return UIA_ProgressBarControlTypeId;
|
||||
case AccessibilityRole::group: return UIA_GroupControlTypeId;
|
||||
case AccessibilityRole::scrollBar: return UIA_ScrollBarControlTypeId;
|
||||
case AccessibilityRole::tooltip: return UIA_ToolTipControlTypeId;
|
||||
|
||||
case AccessibilityRole::ignored:
|
||||
case AccessibilityRole::unspecified: break;
|
||||
};
|
||||
|
||||
return UIA_CustomControlTypeId;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AccessibilityNativeHandle::AccessibilityNativeHandle (AccessibilityHandler& handler)
|
||||
: ComBaseClassHelper (0),
|
||||
accessibilityHandler (handler)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::QueryInterface (REFIID refId, void** result)
|
||||
{
|
||||
*result = nullptr;
|
||||
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if ((refId == __uuidof (ComTypes::IRawElementProviderFragmentRoot) && ! isFragmentRoot()))
|
||||
return E_NOINTERFACE;
|
||||
|
||||
return ComBaseClassHelper::QueryInterface (refId, result);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::get_HostRawElementProvider (IRawElementProviderSimple** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
{
|
||||
if (isFragmentRoot())
|
||||
return wrapper->hostProviderFromHwnd ((HWND) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
|
||||
|
||||
if (auto* embeddedWindow = static_cast<HWND> (AccessibilityHandler::getNativeChildForComponent (accessibilityHandler.getComponent())))
|
||||
return wrapper->hostProviderFromHwnd (embeddedWindow, pRetVal);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions* options)
|
||||
{
|
||||
if (options == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
*options = (ProviderOptions) (ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading);
|
||||
|
||||
if (AccessibilityHandler::getNativeChildForComponent (accessibilityHandler.getComponent()) != nullptr)
|
||||
*options = (ProviderOptions) (*options | ProviderOptions_OverrideProvider);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetPatternProvider (PATTERNID pId, IUnknown** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = [&]() -> IUnknown*
|
||||
{
|
||||
const auto role = accessibilityHandler.getRole();
|
||||
const auto fragmentRoot = isFragmentRoot();
|
||||
|
||||
const auto isListOrTableCell = [] (auto& handler)
|
||||
{
|
||||
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&handler, &AccessibilityHandler::getTableInterface))
|
||||
{
|
||||
if (auto* tableInterface = tableHandler->getTableInterface())
|
||||
{
|
||||
const auto row = tableInterface->getRowSpan (handler);
|
||||
const auto column = tableInterface->getColumnSpan (handler);
|
||||
|
||||
return row.hasValue() && column.hasValue();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
using namespace ComTypes::Constants;
|
||||
|
||||
switch (pId)
|
||||
{
|
||||
case UIA_WindowPatternId:
|
||||
{
|
||||
if (fragmentRoot)
|
||||
return new UIAWindowProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_TransformPatternId:
|
||||
{
|
||||
if (fragmentRoot)
|
||||
return new UIATransformProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_TextPatternId:
|
||||
case UIA_TextPattern2Id:
|
||||
{
|
||||
if (accessibilityHandler.getTextInterface() != nullptr)
|
||||
return new UIATextProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_ValuePatternId:
|
||||
{
|
||||
if (accessibilityHandler.getValueInterface() != nullptr)
|
||||
return new UIAValueProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_RangeValuePatternId:
|
||||
{
|
||||
if (accessibilityHandler.getValueInterface() != nullptr
|
||||
&& accessibilityHandler.getValueInterface()->getRange().isValid())
|
||||
{
|
||||
return new UIARangeValueProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_TogglePatternId:
|
||||
{
|
||||
if (accessibilityHandler.getCurrentState().isCheckable()
|
||||
&& (accessibilityHandler.getActions().contains (AccessibilityActionType::toggle)
|
||||
|| accessibilityHandler.getActions().contains (AccessibilityActionType::press)))
|
||||
{
|
||||
return new UIAToggleProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_SelectionPatternId:
|
||||
{
|
||||
if (role == AccessibilityRole::list
|
||||
|| role == AccessibilityRole::popupMenu
|
||||
|| role == AccessibilityRole::tree)
|
||||
{
|
||||
return new UIASelectionProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_SelectionItemPatternId:
|
||||
{
|
||||
auto state = accessibilityHandler.getCurrentState();
|
||||
|
||||
if (state.isSelectable() || state.isMultiSelectable() || role == AccessibilityRole::radioButton)
|
||||
{
|
||||
return new UIASelectionItemProvider (this);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_TablePatternId:
|
||||
case UIA_GridPatternId:
|
||||
{
|
||||
if (accessibilityHandler.getTableInterface() != nullptr
|
||||
&& (pId == UIA_GridPatternId || accessibilityHandler.getRole() == AccessibilityRole::table))
|
||||
return static_cast<ComTypes::IGridProvider*> (new UIAGridProvider (this));
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_TableItemPatternId:
|
||||
case UIA_GridItemPatternId:
|
||||
{
|
||||
if (isListOrTableCell (accessibilityHandler))
|
||||
return static_cast<ComTypes::IGridItemProvider*> (new UIAGridItemProvider (this));
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_InvokePatternId:
|
||||
{
|
||||
if (accessibilityHandler.getActions().contains (AccessibilityActionType::press))
|
||||
return new UIAInvokeProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_ExpandCollapsePatternId:
|
||||
{
|
||||
if (accessibilityHandler.getActions().contains (AccessibilityActionType::showMenu)
|
||||
&& accessibilityHandler.getCurrentState().isExpandable())
|
||||
return new UIAExpandCollapseProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_ScrollPatternId:
|
||||
{
|
||||
if (accessibilityHandler.getTableInterface() != nullptr)
|
||||
return new UIAScrollProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_ScrollItemPatternId:
|
||||
{
|
||||
if (isListOrTableCell (accessibilityHandler))
|
||||
return new UIAScrollItemProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
VariantHelpers::clear (pRetVal);
|
||||
|
||||
const auto role = accessibilityHandler.getRole();
|
||||
const auto state = accessibilityHandler.getCurrentState();
|
||||
const auto ignored = accessibilityHandler.isIgnored();
|
||||
|
||||
using namespace ComTypes::Constants;
|
||||
|
||||
switch (propertyId)
|
||||
{
|
||||
case UIA_AutomationIdPropertyId:
|
||||
VariantHelpers::setString (getAutomationId (accessibilityHandler), pRetVal);
|
||||
break;
|
||||
case UIA_ControlTypePropertyId:
|
||||
VariantHelpers::setInt (roleToControlTypeId (role), pRetVal);
|
||||
break;
|
||||
case UIA_FrameworkIdPropertyId:
|
||||
VariantHelpers::setString ("JUCE", pRetVal);
|
||||
break;
|
||||
case UIA_FullDescriptionPropertyId:
|
||||
VariantHelpers::setString (accessibilityHandler.getDescription(), pRetVal);
|
||||
break;
|
||||
case UIA_HelpTextPropertyId:
|
||||
VariantHelpers::setString (accessibilityHandler.getHelp(), pRetVal);
|
||||
break;
|
||||
case UIA_IsContentElementPropertyId:
|
||||
VariantHelpers::setBool (! ignored && accessibilityHandler.isVisibleWithinParent(),
|
||||
pRetVal);
|
||||
break;
|
||||
case UIA_IsControlElementPropertyId:
|
||||
VariantHelpers::setBool (true, pRetVal);
|
||||
break;
|
||||
case UIA_IsDialogPropertyId:
|
||||
VariantHelpers::setBool (role == AccessibilityRole::dialogWindow, pRetVal);
|
||||
break;
|
||||
case UIA_IsEnabledPropertyId:
|
||||
VariantHelpers::setBool (accessibilityHandler.getComponent().isEnabled(), pRetVal);
|
||||
break;
|
||||
case UIA_IsKeyboardFocusablePropertyId:
|
||||
VariantHelpers::setBool (state.isFocusable(), pRetVal);
|
||||
break;
|
||||
case UIA_HasKeyboardFocusPropertyId:
|
||||
VariantHelpers::setBool (accessibilityHandler.hasFocus (true), pRetVal);
|
||||
break;
|
||||
case UIA_IsOffscreenPropertyId:
|
||||
VariantHelpers::setBool (! accessibilityHandler.isVisibleWithinParent(), pRetVal);
|
||||
break;
|
||||
case UIA_IsPasswordPropertyId:
|
||||
if (auto* textInterface = accessibilityHandler.getTextInterface())
|
||||
VariantHelpers::setBool (textInterface->isDisplayingProtectedText(), pRetVal);
|
||||
|
||||
break;
|
||||
case UIA_IsPeripheralPropertyId:
|
||||
VariantHelpers::setBool (role == AccessibilityRole::tooltip
|
||||
|| role == AccessibilityRole::popupMenu
|
||||
|| role == AccessibilityRole::splashScreen,
|
||||
pRetVal);
|
||||
break;
|
||||
case UIA_NamePropertyId:
|
||||
if (! ignored)
|
||||
VariantHelpers::setString (getElementName(), pRetVal);
|
||||
|
||||
break;
|
||||
case UIA_ProcessIdPropertyId:
|
||||
VariantHelpers::setInt ((int) GetCurrentProcessId(), pRetVal);
|
||||
break;
|
||||
case UIA_NativeWindowHandlePropertyId:
|
||||
if (isFragmentRoot())
|
||||
VariantHelpers::setInt ((int) (pointer_sized_int) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::Navigate (ComTypes::NavigateDirection direction, ComTypes::IRawElementProviderFragment** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto* handler = [&]() -> AccessibilityHandler*
|
||||
{
|
||||
if (direction == ComTypes::NavigateDirection_Parent)
|
||||
return accessibilityHandler.getParent();
|
||||
|
||||
if (direction == ComTypes::NavigateDirection_FirstChild
|
||||
|| direction == ComTypes::NavigateDirection_LastChild)
|
||||
{
|
||||
auto children = accessibilityHandler.getChildren();
|
||||
|
||||
return children.empty() ? nullptr
|
||||
: (direction == ComTypes::NavigateDirection_FirstChild ? children.front()
|
||||
: children.back());
|
||||
}
|
||||
|
||||
if (direction == ComTypes::NavigateDirection_NextSibling
|
||||
|| direction == ComTypes::NavigateDirection_PreviousSibling)
|
||||
{
|
||||
if (auto* parent = accessibilityHandler.getParent())
|
||||
{
|
||||
const auto siblings = parent->getChildren();
|
||||
const auto iter = std::find (siblings.cbegin(), siblings.cend(), &accessibilityHandler);
|
||||
|
||||
if (iter == siblings.end())
|
||||
return nullptr;
|
||||
|
||||
if (direction == ComTypes::NavigateDirection_NextSibling && iter != std::prev (siblings.cend()))
|
||||
return *std::next (iter);
|
||||
|
||||
if (direction == ComTypes::NavigateDirection_PreviousSibling && iter != siblings.cbegin())
|
||||
return *std::prev (iter);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
if (handler != nullptr)
|
||||
if (auto* provider = handler->getNativeImplementation())
|
||||
if (provider->isElementValid())
|
||||
provider->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetRuntimeId (SAFEARRAY** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (! isFragmentRoot())
|
||||
{
|
||||
*pRetVal = SafeArrayCreateVector (VT_I4, 0, 2);
|
||||
|
||||
if (*pRetVal == nullptr)
|
||||
return E_OUTOFMEMORY;
|
||||
|
||||
for (LONG i = 0; i < 2; ++i)
|
||||
{
|
||||
auto hr = SafeArrayPutElement (*pRetVal, &i, &rtid[(size_t) i]);
|
||||
|
||||
if (FAILED (hr))
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::get_BoundingRectangle (ComTypes::UiaRect* pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto bounds = Desktop::getInstance().getDisplays()
|
||||
.logicalToPhysical (accessibilityHandler.getComponent().getScreenBounds());
|
||||
|
||||
pRetVal->left = bounds.getX();
|
||||
pRetVal->top = bounds.getY();
|
||||
pRetVal->width = bounds.getWidth();
|
||||
pRetVal->height = bounds.getHeight();
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::SetFocus()
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const WeakReference<Component> safeComponent (&accessibilityHandler.getComponent());
|
||||
|
||||
accessibilityHandler.getActions().invoke (AccessibilityActionType::focus);
|
||||
|
||||
if (safeComponent != nullptr)
|
||||
accessibilityHandler.grabFocus();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::get_FragmentRoot (ComTypes::IRawElementProviderFragmentRoot** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
auto* handler = [&]() -> AccessibilityHandler*
|
||||
{
|
||||
if (isFragmentRoot())
|
||||
return &accessibilityHandler;
|
||||
|
||||
if (auto* peer = accessibilityHandler.getComponent().getPeer())
|
||||
return peer->getComponent().getAccessibilityHandler();
|
||||
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
if (handler != nullptr)
|
||||
{
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::ElementProviderFromPoint (double x, double y, ComTypes::IRawElementProviderFragment** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto* handler = [&]
|
||||
{
|
||||
auto logicalScreenPoint = Desktop::getInstance().getDisplays()
|
||||
.physicalToLogical (Point<int> (roundToInt (x),
|
||||
roundToInt (y)));
|
||||
|
||||
if (auto* child = accessibilityHandler.getChildAt (logicalScreenPoint))
|
||||
return child;
|
||||
|
||||
return &accessibilityHandler;
|
||||
}();
|
||||
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetFocus (ComTypes::IRawElementProviderFragment** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
const auto getFocusHandler = [this]() -> AccessibilityHandler*
|
||||
{
|
||||
if (auto* modal = Component::getCurrentlyModalComponent())
|
||||
{
|
||||
const auto& component = accessibilityHandler.getComponent();
|
||||
|
||||
if (! component.isParentOf (modal)
|
||||
&& component.isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if (auto* modalHandler = modal->getAccessibilityHandler())
|
||||
{
|
||||
if (auto* focusChild = modalHandler->getChildFocus())
|
||||
return focusChild;
|
||||
|
||||
return modalHandler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* focusChild = accessibilityHandler.getChildFocus())
|
||||
return focusChild;
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (auto* focusHandler = getFocusHandler())
|
||||
focusHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetOverrideProviderForHwnd (HWND hwnd, IRawElementProviderSimple** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (auto* component = AccessibilityHandler::getComponentForNativeChild (hwnd))
|
||||
if (auto* handler = component->getAccessibilityHandler())
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String AccessibilityNativeHandle::getElementName() const
|
||||
{
|
||||
if (accessibilityHandler.getRole() == AccessibilityRole::tooltip)
|
||||
return accessibilityHandler.getDescription();
|
||||
|
||||
auto name = accessibilityHandler.getTitle();
|
||||
|
||||
if (name.isEmpty() && isFragmentRoot())
|
||||
return detail::AccessibilityHelpers::getApplicationOrPluginName();
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class AccessibilityNativeHandle : public ComBaseClassHelper<IRawElementProviderSimple,
|
||||
ComTypes::IRawElementProviderFragment,
|
||||
ComTypes::IRawElementProviderFragmentRoot,
|
||||
ComTypes::IRawElementProviderHwndOverride>
|
||||
{
|
||||
public:
|
||||
explicit AccessibilityNativeHandle (AccessibilityHandler& handler);
|
||||
|
||||
//==============================================================================
|
||||
void invalidateElement() noexcept { valid = false; }
|
||||
bool isElementValid() const noexcept { return valid; }
|
||||
|
||||
const AccessibilityHandler& getHandler() { return accessibilityHandler; }
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT QueryInterface (REFIID refId, void** result) override;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT get_HostRawElementProvider (IRawElementProviderSimple** provider) override;
|
||||
JUCE_COMRESULT get_ProviderOptions (ProviderOptions* options) override;
|
||||
JUCE_COMRESULT GetPatternProvider (PATTERNID pId, IUnknown** provider) override;
|
||||
JUCE_COMRESULT GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal) override;
|
||||
|
||||
JUCE_COMRESULT Navigate (ComTypes::NavigateDirection direction, ComTypes::IRawElementProviderFragment** pRetVal) override;
|
||||
JUCE_COMRESULT GetRuntimeId (SAFEARRAY** pRetVal) override;
|
||||
JUCE_COMRESULT get_BoundingRectangle (ComTypes::UiaRect* pRetVal) override;
|
||||
JUCE_COMRESULT GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal) override;
|
||||
JUCE_COMRESULT SetFocus() override;
|
||||
JUCE_COMRESULT get_FragmentRoot (ComTypes::IRawElementProviderFragmentRoot** pRetVal) override;
|
||||
|
||||
JUCE_COMRESULT ElementProviderFromPoint (double x, double y, ComTypes::IRawElementProviderFragment** pRetVal) override;
|
||||
JUCE_COMRESULT GetFocus (ComTypes::IRawElementProviderFragment** pRetVal) override;
|
||||
|
||||
JUCE_COMRESULT GetOverrideProviderForHwnd (HWND hwnd, IRawElementProviderSimple** pRetVal) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String getElementName() const;
|
||||
bool isFragmentRoot() const { return accessibilityHandler.getComponent().isOnDesktop(); }
|
||||
|
||||
//==============================================================================
|
||||
AccessibilityHandler& accessibilityHandler;
|
||||
|
||||
static int idCounter;
|
||||
std::array<int, 2> rtid { UiaAppendRuntimeId, ++idCounter };
|
||||
bool valid = true;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeHandle)
|
||||
};
|
||||
|
||||
}
|
||||
Vendored
+274
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
struct AccessibleObjCClassDeleter
|
||||
{
|
||||
template <typename ElementType>
|
||||
void operator() (ElementType* element) const
|
||||
{
|
||||
juceFreeAccessibilityPlatformSpecificData (element);
|
||||
|
||||
object_setInstanceVariable (element, "handler", nullptr);
|
||||
[element release];
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Base>
|
||||
class AccessibleObjCClass : public ObjCClass<Base>
|
||||
{
|
||||
public:
|
||||
using Holder = std::unique_ptr<Base, AccessibleObjCClassDeleter>;
|
||||
|
||||
protected:
|
||||
AccessibleObjCClass() : AccessibleObjCClass ("JUCEAccessibilityElement_") {}
|
||||
|
||||
explicit AccessibleObjCClass (const char* name) : ObjCClass<Base> (name)
|
||||
{
|
||||
ObjCClass<Base>::template addIvar<AccessibilityHandler*> ("handler");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static AccessibilityHandler* getHandler (id self)
|
||||
{
|
||||
return getIvar<AccessibilityHandler*> (self, "handler");
|
||||
}
|
||||
|
||||
template <typename MemberFn>
|
||||
static auto getInterface (id self, MemberFn fn) noexcept -> decltype ((std::declval<AccessibilityHandler>().*fn)())
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return (handler->*fn)();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static AccessibilityTextInterface* getTextInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getTextInterface); }
|
||||
static AccessibilityValueInterface* getValueInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getValueInterface); }
|
||||
static AccessibilityTableInterface* getTableInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getTableInterface); }
|
||||
static AccessibilityCellInterface* getCellInterface (id self) noexcept { return getInterface (self, &AccessibilityHandler::getCellInterface); }
|
||||
|
||||
static bool hasEditableText (AccessibilityHandler& handler) noexcept
|
||||
{
|
||||
return handler.getRole() == AccessibilityRole::editableText
|
||||
&& handler.getTextInterface() != nullptr
|
||||
&& ! handler.getTextInterface()->isReadOnly();
|
||||
}
|
||||
|
||||
static id getAccessibilityValueFromInterfaces (const AccessibilityHandler& handler)
|
||||
{
|
||||
if (auto* textInterface = handler.getTextInterface())
|
||||
return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
|
||||
|
||||
if (auto* valueInterface = handler.getValueInterface())
|
||||
return juceStringToNS (valueInterface->getCurrentValueAsString());
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static BOOL getIsAccessibilityElement (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return ! handler->isIgnored() && handler->getRole() != AccessibilityRole::window;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static void setAccessibilityValue (id self, SEL, NSString* value)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (hasEditableText (*handler))
|
||||
{
|
||||
handler->getTextInterface()->setText (nsStringToJuce (value));
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto* valueInterface = handler->getValueInterface())
|
||||
if (! valueInterface->isReadOnly())
|
||||
valueInterface->setValueAsString (nsStringToJuce (value));
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL performActionIfSupported (id self, AccessibilityActionType actionType)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
if (handler->getActions().invoke (actionType))
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static BOOL accessibilityPerformPress (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
if (handler->getCurrentState().isCheckable() && handler->getActions().invoke (AccessibilityActionType::toggle))
|
||||
return YES;
|
||||
|
||||
return performActionIfSupported (self, AccessibilityActionType::press);
|
||||
}
|
||||
|
||||
static BOOL accessibilityPerformIncrement (id self, SEL)
|
||||
{
|
||||
if (auto* valueInterface = getValueInterface (self))
|
||||
{
|
||||
if (! valueInterface->isReadOnly())
|
||||
{
|
||||
auto range = valueInterface->getRange();
|
||||
|
||||
if (range.isValid())
|
||||
{
|
||||
valueInterface->setValue (jlimit (range.getMinimumValue(),
|
||||
range.getMaximumValue(),
|
||||
valueInterface->getCurrentValue() + range.getInterval()));
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static BOOL accessibilityPerformDecrement (id self, SEL)
|
||||
{
|
||||
if (auto* valueInterface = getValueInterface (self))
|
||||
{
|
||||
if (! valueInterface->isReadOnly())
|
||||
{
|
||||
auto range = valueInterface->getRange();
|
||||
|
||||
if (range.isValid())
|
||||
{
|
||||
valueInterface->setValue (jlimit (range.getMinimumValue(),
|
||||
range.getMaximumValue(),
|
||||
valueInterface->getCurrentValue() - range.getInterval()));
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static NSString* getAccessibilityTitle (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
auto title = handler->getTitle();
|
||||
|
||||
if (title.isEmpty() && handler->getComponent().isOnDesktop())
|
||||
title = detail::AccessibilityHelpers::getApplicationOrPluginName();
|
||||
|
||||
NSString* nsString = juceStringToNS (title);
|
||||
|
||||
if (nsString != nil && [[self accessibilityValue] isEqual: nsString])
|
||||
return @"";
|
||||
|
||||
return nsString;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSString* getAccessibilityHelp (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return juceStringToNS (handler->getHelp());
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static BOOL getIsAccessibilityModal (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return handler->getComponent().isCurrentlyModal();
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static NSInteger getAccessibilityRowCount (id self, SEL)
|
||||
{
|
||||
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
|
||||
if (auto* tableInterface = tableHandler->getTableInterface())
|
||||
return tableInterface->getNumRows();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NSInteger getAccessibilityColumnCount (id self, SEL)
|
||||
{
|
||||
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
|
||||
if (auto* tableInterface = tableHandler->getTableInterface())
|
||||
return tableInterface->getNumColumns();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Getter>
|
||||
static NSRange getCellDimensions (id self, Getter getter)
|
||||
{
|
||||
const auto notFound = NSMakeRange (NSNotFound, 0);
|
||||
|
||||
auto* handler = getHandler (self);
|
||||
|
||||
if (handler == nullptr)
|
||||
return notFound;
|
||||
|
||||
auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface);
|
||||
|
||||
if (tableHandler == nullptr)
|
||||
return notFound;
|
||||
|
||||
auto* tableInterface = tableHandler->getTableInterface();
|
||||
|
||||
if (tableInterface == nullptr)
|
||||
return notFound;
|
||||
|
||||
const auto result = (tableInterface->*getter) (*handler);
|
||||
|
||||
if (! result.hasValue())
|
||||
return notFound;
|
||||
|
||||
return NSMakeRange ((NSUInteger) result->begin, (NSUInteger) result->num);
|
||||
}
|
||||
|
||||
static NSRange getAccessibilityRowIndexRange (id self, SEL)
|
||||
{
|
||||
return getCellDimensions (self, &AccessibilityTableInterface::getRowSpan);
|
||||
}
|
||||
|
||||
static NSRange getAccessibilityColumnIndexRange (id self, SEL)
|
||||
{
|
||||
return getCellDimensions (self, &AccessibilityTableInterface::getColumnSpan);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibleObjCClass)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+302
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct AccessibilityTextHelpers
|
||||
{
|
||||
/* Wraps a CharPtr into a stdlib-compatible iterator.
|
||||
|
||||
MSVC's std::reverse_iterator requires the wrapped iterator to be default constructible
|
||||
when building in C++20 mode, but I don't really want to add public default constructors to
|
||||
the CharPtr types. Instead, we add a very basic default constructor here which sets the
|
||||
wrapped CharPtr to nullptr.
|
||||
*/
|
||||
template <typename CharPtr>
|
||||
class CharPtrIteratorAdapter
|
||||
{
|
||||
public:
|
||||
using difference_type = int;
|
||||
using value_type = decltype (*std::declval<CharPtr>());
|
||||
using pointer = value_type*;
|
||||
using reference = value_type;
|
||||
using iterator_category = std::bidirectional_iterator_tag;
|
||||
|
||||
CharPtrIteratorAdapter() = default;
|
||||
constexpr explicit CharPtrIteratorAdapter (CharPtr arg) : ptr (arg) {}
|
||||
|
||||
constexpr auto operator*() const { return *ptr; }
|
||||
|
||||
constexpr CharPtrIteratorAdapter& operator++()
|
||||
{
|
||||
++ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr CharPtrIteratorAdapter& operator--()
|
||||
{
|
||||
--ptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool operator== (const CharPtrIteratorAdapter& other) const { return ptr == other.ptr; }
|
||||
constexpr bool operator!= (const CharPtrIteratorAdapter& other) const { return ptr != other.ptr; }
|
||||
|
||||
constexpr auto operator+ (difference_type offset) const { return CharPtrIteratorAdapter { ptr + offset }; }
|
||||
constexpr auto operator- (difference_type offset) const { return CharPtrIteratorAdapter { ptr - offset }; }
|
||||
|
||||
private:
|
||||
CharPtr ptr { {} };
|
||||
};
|
||||
|
||||
template <typename CharPtr>
|
||||
static auto makeCharPtrIteratorAdapter (CharPtr ptr)
|
||||
{
|
||||
return CharPtrIteratorAdapter<CharPtr> { ptr };
|
||||
}
|
||||
|
||||
enum class BoundaryType
|
||||
{
|
||||
character,
|
||||
word,
|
||||
line,
|
||||
document
|
||||
};
|
||||
|
||||
enum class Direction
|
||||
{
|
||||
forwards,
|
||||
backwards
|
||||
};
|
||||
|
||||
enum class ExtendSelection
|
||||
{
|
||||
no,
|
||||
yes
|
||||
};
|
||||
|
||||
/* Indicates whether a function may return the current text position, in the case that the
|
||||
position already falls on a text unit boundary.
|
||||
*/
|
||||
enum class IncludeThisBoundary
|
||||
{
|
||||
no, //< Always search for the following boundary, even if the current position falls on a boundary
|
||||
yes //< Return the current position if it falls on a boundary
|
||||
};
|
||||
|
||||
/* Indicates whether a word boundary should include any whitespaces that follow the
|
||||
non-whitespace characters.
|
||||
*/
|
||||
enum class IncludeWhitespaceAfterWords
|
||||
{
|
||||
no, //< The word ends on the first whitespace character
|
||||
yes //< The word ends after the last whitespace character
|
||||
};
|
||||
|
||||
/* Like std::distance, but always does an O(N) count rather than an O(1) count, and doesn't
|
||||
require the iterators to have any member type aliases.
|
||||
*/
|
||||
template <typename Iter>
|
||||
static int countDifference (Iter from, Iter to)
|
||||
{
|
||||
int distance = 0;
|
||||
|
||||
while (from != to)
|
||||
{
|
||||
++from;
|
||||
++distance;
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
/* Returns the number of characters between ptr and the next word end in a specific
|
||||
direction.
|
||||
|
||||
If ptr is inside a word, the result will be the distance to the end of the same
|
||||
word.
|
||||
*/
|
||||
template <typename CharPtr>
|
||||
static int findNextWordEndOffset (CharPtr beginIn,
|
||||
CharPtr endIn,
|
||||
CharPtr ptrIn,
|
||||
Direction direction,
|
||||
IncludeThisBoundary includeBoundary,
|
||||
IncludeWhitespaceAfterWords includeWhitespace)
|
||||
{
|
||||
const auto begin = makeCharPtrIteratorAdapter (beginIn);
|
||||
const auto end = makeCharPtrIteratorAdapter (endIn);
|
||||
const auto ptr = makeCharPtrIteratorAdapter (ptrIn);
|
||||
|
||||
const auto move = [&] (auto b, auto e, auto iter)
|
||||
{
|
||||
const auto isSpace = [] (juce_wchar c) { return CharacterFunctions::isWhitespace (c); };
|
||||
|
||||
const auto start = [&]
|
||||
{
|
||||
if (iter == b && includeBoundary == IncludeThisBoundary::yes)
|
||||
return b;
|
||||
|
||||
const auto nudged = iter - (iter != b && includeBoundary == IncludeThisBoundary::yes ? 1 : 0);
|
||||
|
||||
return includeWhitespace == IncludeWhitespaceAfterWords::yes
|
||||
? std::find_if (nudged, e, isSpace)
|
||||
: std::find_if_not (nudged, e, isSpace);
|
||||
}();
|
||||
|
||||
const auto found = includeWhitespace == IncludeWhitespaceAfterWords::yes
|
||||
? std::find_if_not (start, e, isSpace)
|
||||
: std::find_if (start, e, isSpace);
|
||||
|
||||
return countDifference (iter, found);
|
||||
};
|
||||
|
||||
return direction == Direction::forwards ? move (begin, end, ptr)
|
||||
: -move (std::make_reverse_iterator (end),
|
||||
std::make_reverse_iterator (begin),
|
||||
std::make_reverse_iterator (ptr));
|
||||
}
|
||||
|
||||
/* Returns the number of characters between ptr and the beginning of the next line in a
|
||||
specific direction.
|
||||
*/
|
||||
template <typename CharPtr>
|
||||
static int findNextLineOffset (CharPtr beginIn,
|
||||
CharPtr endIn,
|
||||
CharPtr ptrIn,
|
||||
Direction direction,
|
||||
IncludeThisBoundary includeBoundary)
|
||||
{
|
||||
const auto begin = makeCharPtrIteratorAdapter (beginIn);
|
||||
const auto end = makeCharPtrIteratorAdapter (endIn);
|
||||
const auto ptr = makeCharPtrIteratorAdapter (ptrIn);
|
||||
|
||||
const auto findNewline = [] (auto from, auto to) { return std::find (from, to, juce_wchar { '\n' }); };
|
||||
|
||||
if (direction == Direction::forwards)
|
||||
{
|
||||
if (ptr != begin && includeBoundary == IncludeThisBoundary::yes && *(ptr - 1) == '\n')
|
||||
return 0;
|
||||
|
||||
const auto newline = findNewline (ptr, end);
|
||||
return countDifference (ptr, newline) + (newline == end ? 0 : 1);
|
||||
}
|
||||
|
||||
const auto rbegin = std::make_reverse_iterator (ptr);
|
||||
const auto rend = std::make_reverse_iterator (begin);
|
||||
|
||||
return -countDifference (rbegin, findNewline (rbegin + (rbegin == rend || includeBoundary == IncludeThisBoundary::yes ? 0 : 1), rend));
|
||||
}
|
||||
|
||||
/* Unfortunately, the method of computing end-points of text units depends on context, and on
|
||||
the current platform.
|
||||
|
||||
Some examples of different behaviour:
|
||||
- On Android, updating the cursor/selection always searches for the next text unit boundary;
|
||||
but on Windows, ExpandToEnclosingUnit() should not move the starting point of the
|
||||
selection if it already at a unit boundary. This means that we need both inclusive and
|
||||
exclusive methods for finding the next text boundary.
|
||||
- On Android, moving the cursor by 'words' should move to the first space following a
|
||||
non-space character in the requested direction. On Windows, a 'word' includes trailing
|
||||
whitespace, but not preceding whitespace. This means that we need a way of specifying
|
||||
whether whitespace should be included when navigating by words.
|
||||
*/
|
||||
static int findTextBoundary (const AccessibilityTextInterface& textInterface,
|
||||
int currentPosition,
|
||||
BoundaryType boundary,
|
||||
Direction direction,
|
||||
IncludeThisBoundary includeBoundary,
|
||||
IncludeWhitespaceAfterWords includeWhitespace)
|
||||
{
|
||||
const auto numCharacters = textInterface.getTotalNumCharacters();
|
||||
const auto isForwards = (direction == Direction::forwards);
|
||||
const auto currentClamped = jlimit (0, numCharacters, currentPosition);
|
||||
|
||||
switch (boundary)
|
||||
{
|
||||
case BoundaryType::character:
|
||||
{
|
||||
const auto offset = includeBoundary == IncludeThisBoundary::yes ? 0
|
||||
: (isForwards ? 1 : -1);
|
||||
return jlimit (0, numCharacters, currentPosition + offset);
|
||||
}
|
||||
|
||||
case BoundaryType::word:
|
||||
{
|
||||
const auto str = textInterface.getText ({ 0, numCharacters });
|
||||
return currentClamped + findNextWordEndOffset (str.begin(),
|
||||
str.end(),
|
||||
str.begin() + currentClamped,
|
||||
direction,
|
||||
includeBoundary,
|
||||
includeWhitespace);
|
||||
}
|
||||
|
||||
case BoundaryType::line:
|
||||
{
|
||||
const auto str = textInterface.getText ({ 0, numCharacters });
|
||||
return currentClamped + findNextLineOffset (str.begin(),
|
||||
str.end(),
|
||||
str.begin() + currentClamped,
|
||||
direction,
|
||||
includeBoundary);
|
||||
}
|
||||
|
||||
case BoundaryType::document:
|
||||
return isForwards ? numCharacters : 0;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Adjusts the current text selection range, using an algorithm appropriate for cursor movement
|
||||
on Android.
|
||||
*/
|
||||
static Range<int> findNewSelectionRangeAndroid (const AccessibilityTextInterface& textInterface,
|
||||
BoundaryType boundaryType,
|
||||
ExtendSelection extend,
|
||||
Direction direction)
|
||||
{
|
||||
const auto oldPos = textInterface.getTextInsertionOffset();
|
||||
const auto cursorPos = findTextBoundary (textInterface,
|
||||
oldPos,
|
||||
boundaryType,
|
||||
direction,
|
||||
IncludeThisBoundary::no,
|
||||
IncludeWhitespaceAfterWords::no);
|
||||
|
||||
if (extend == ExtendSelection::no)
|
||||
return { cursorPos, cursorPos };
|
||||
|
||||
const auto currentSelection = textInterface.getSelection();
|
||||
const auto start = currentSelection.getStart();
|
||||
const auto end = currentSelection.getEnd();
|
||||
return Range<int>::between (cursorPos, oldPos == start ? end : start);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
third_party/JUCE/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers_test.cpp
Vendored
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct AccessibilityTextHelpersTest final : public UnitTest
|
||||
{
|
||||
AccessibilityTextHelpersTest()
|
||||
: UnitTest ("AccessibilityTextHelpers", UnitTestCategories::gui) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
using ATH = AccessibilityTextHelpers;
|
||||
|
||||
beginTest ("Android find word end");
|
||||
{
|
||||
const auto testMultiple = [this] (String str,
|
||||
int start,
|
||||
const std::vector<int>& collection)
|
||||
{
|
||||
auto it = collection.begin();
|
||||
|
||||
for (const auto direction : { ATH::Direction::forwards, ATH::Direction::backwards })
|
||||
{
|
||||
for (const auto includeBoundary : { ATH::IncludeThisBoundary::no, ATH::IncludeThisBoundary::yes })
|
||||
{
|
||||
for (const auto includeWhitespace : { ATH::IncludeWhitespaceAfterWords::no, ATH::IncludeWhitespaceAfterWords::yes })
|
||||
{
|
||||
const auto actual = ATH::findNextWordEndOffset (str.begin(), str.end(), str.begin() + start, direction, includeBoundary, includeWhitespace);
|
||||
const auto expected = *it++;
|
||||
expect (expected == actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Character Indices 0 3 56 13 50 51
|
||||
// | | || | | |
|
||||
const auto string = String ("hello world \r\n with some spaces in this sentence ") + String (CharPointer_UTF8 ("\xe2\x88\xae E\xe2\x8b\x85""da = Q"));
|
||||
// Direction forwards forwards forwards forwards backwards backwards backwards backwards
|
||||
// IncludeBoundary no no yes yes no no yes yes
|
||||
// IncludeWhitespace no yes no yes no yes no yes
|
||||
testMultiple (string, 0, { 5, 6, 5, 0, 0, 0, 0, 0 });
|
||||
testMultiple (string, 3, { 2, 3, 2, 3, -3, -3, -3, -3 });
|
||||
testMultiple (string, 5, { 6, 1, 0, 1, -5, -5, -5, 0 });
|
||||
testMultiple (string, 6, { 5, 9, 5, 0, -6, -1, 0, -1 });
|
||||
testMultiple (string, 13, { 6, 2, 6, 2, -7, -2, -7, -2 });
|
||||
testMultiple (string, 50, { 1, 2, 1, 0, -9, -1, 0, -1 });
|
||||
testMultiple (string, 51, { 5, 1, 0, 1, -1, -2, -1, 0 });
|
||||
|
||||
testMultiple (" a b ", 0, { 3, 2, 0, 2, 0, 0, 0, 0 });
|
||||
testMultiple (" a b ", 1, { 2, 1, 2, 1, -1, -1, -1, -1 });
|
||||
}
|
||||
|
||||
beginTest ("Android text range adjustment");
|
||||
{
|
||||
const auto testMultiple = [this] (String str,
|
||||
Range<int> initial,
|
||||
auto boundary,
|
||||
const std::vector<Range<int>>& collection)
|
||||
{
|
||||
auto it = collection.begin();
|
||||
|
||||
for (auto extend : { ATH::ExtendSelection::no, ATH::ExtendSelection::yes })
|
||||
{
|
||||
for (auto direction : { ATH::Direction::forwards, ATH::Direction::backwards })
|
||||
{
|
||||
for (auto insert : { CursorPosition::begin, CursorPosition::end })
|
||||
{
|
||||
const MockAccessibilityTextInterface mock { str, initial, insert };
|
||||
const auto actual = ATH::findNewSelectionRangeAndroid (mock, boundary, extend, direction);
|
||||
const auto expected = *it++;
|
||||
expect (expected == actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Extend no no no no yes yes yes yes
|
||||
// Direction forwards forwards backwards backwards forwards forwards backwards backwards
|
||||
// Insert begin end begin end begin end begin end
|
||||
testMultiple ("hello world", { 5, 5 }, ATH::BoundaryType::character, { { 6, 6 }, { 6, 6 }, { 4, 4 }, { 4, 4 }, { 5, 6 }, { 5, 6 }, { 4, 5 }, { 4, 5 } });
|
||||
testMultiple ("hello world", { 0, 0 }, ATH::BoundaryType::character, { { 1, 1 }, { 1, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 }, { 0, 1 }, { 0, 0 }, { 0, 0 } });
|
||||
testMultiple ("hello world", { 11, 11 }, ATH::BoundaryType::character, { { 11, 11 }, { 11, 11 }, { 10, 10 }, { 10, 10 }, { 11, 11 }, { 11, 11 }, { 10, 11 }, { 10, 11 } });
|
||||
testMultiple ("hello world", { 4, 5 }, ATH::BoundaryType::character, { { 5, 5 }, { 6, 6 }, { 3, 3 }, { 4, 4 }, { 5, 5 }, { 4, 6 }, { 3, 5 }, { 4, 4 } });
|
||||
testMultiple ("hello world", { 0, 1 }, ATH::BoundaryType::character, { { 1, 1 }, { 2, 2 }, { 0, 0 }, { 0, 0 }, { 1, 1 }, { 0, 2 }, { 0, 1 }, { 0, 0 } });
|
||||
testMultiple ("hello world", { 10, 11 }, ATH::BoundaryType::character, { { 11, 11 }, { 11, 11 }, { 9, 9 }, { 10, 10 }, { 11, 11 }, { 10, 11 }, { 9, 11 }, { 10, 10 } });
|
||||
|
||||
testMultiple ("foo bar baz", { 0, 0 }, ATH::BoundaryType::word, { { 3, 3 }, { 3, 3 }, { 0, 0 }, { 0, 0 }, { 0, 3 }, { 0, 3 }, { 0, 0 }, { 0, 0 } });
|
||||
testMultiple ("foo bar baz", { 1, 6 }, ATH::BoundaryType::word, { { 3, 3 }, { 8, 8 }, { 0, 0 }, { 5, 5 }, { 3, 6 }, { 1, 8 }, { 0, 6 }, { 1, 5 } });
|
||||
testMultiple ("foo bar baz", { 3, 3 }, ATH::BoundaryType::word, { { 8, 8 }, { 8, 8 }, { 0, 0 }, { 0, 0 }, { 3, 8 }, { 3, 8 }, { 0, 3 }, { 0, 3 } });
|
||||
testMultiple ("foo bar baz", { 3, 5 }, ATH::BoundaryType::word, { { 8, 8 }, { 8, 8 }, { 0, 0 }, { 0, 0 }, { 5, 8 }, { 3, 8 }, { 0, 5 }, { 0, 3 } });
|
||||
|
||||
testMultiple ("foo bar\n\n\na b\nc d e", { 0, 0 }, ATH::BoundaryType::line, { { 8, 8 }, { 8, 8 }, { 0, 0 }, { 0, 0 }, { 0, 8 }, { 0, 8 }, { 0, 0 }, { 0, 0 } });
|
||||
testMultiple ("foo bar\n\n\na b\nc d e", { 7, 7 }, ATH::BoundaryType::line, { { 8, 8 }, { 8, 8 }, { 0, 0 }, { 0, 0 }, { 7, 8 }, { 7, 8 }, { 0, 7 }, { 0, 7 } });
|
||||
testMultiple ("foo bar\n\n\na b\nc d e", { 8, 8 }, ATH::BoundaryType::line, { { 9, 9 }, { 9, 9 }, { 0, 0 }, { 0, 0 }, { 8, 9 }, { 8, 9 }, { 0, 8 }, { 0, 8 } });
|
||||
|
||||
testMultiple ("foo bar\r\na b\r\nxyz", { 0, 0 }, ATH::BoundaryType::line, { { 9, 9 }, { 9, 9 }, { 0, 0 }, { 0, 0 }, { 0, 9 }, { 0, 9 }, { 0, 0 }, { 0, 0 } });
|
||||
testMultiple ("foo bar\r\na b\r\nxyz", { 10, 10 }, ATH::BoundaryType::line, { { 14, 14 }, { 14, 14 }, { 9, 9 }, { 9, 9 }, { 10, 14 }, { 10, 14 }, { 9, 10 }, { 9, 10 } });
|
||||
}
|
||||
}
|
||||
|
||||
enum class CursorPosition { begin, end };
|
||||
|
||||
class MockAccessibilityTextInterface final : public AccessibilityTextInterface
|
||||
{
|
||||
public:
|
||||
MockAccessibilityTextInterface (String stringIn, Range<int> selectionIn, CursorPosition insertIn)
|
||||
: string (stringIn), selection (selectionIn), insert (insertIn) {}
|
||||
|
||||
bool isDisplayingProtectedText() const override { return false; }
|
||||
bool isReadOnly() const override { return false; }
|
||||
int getTotalNumCharacters() const override { return string.length(); }
|
||||
Range<int> getSelection() const override { return selection; }
|
||||
int getTextInsertionOffset() const override { return insert == CursorPosition::begin ? selection.getStart() : selection.getEnd(); }
|
||||
String getText (Range<int> range) const override { return string.substring (range.getStart(), range.getEnd()); }
|
||||
RectangleList<int> getTextBounds (Range<int>) const override { return {}; }
|
||||
int getOffsetAtPoint (Point<int>) const override { return 0; }
|
||||
|
||||
void setSelection (Range<int> newRange) override { selection = newRange; }
|
||||
void setText (const String& newText) override { string = newText; }
|
||||
|
||||
private:
|
||||
String string;
|
||||
Range<int> selection;
|
||||
CursorPosition insert;
|
||||
};
|
||||
};
|
||||
|
||||
static AccessibilityTextHelpersTest accessibilityTextHelpersTest;
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+1059
File diff suppressed because it is too large
Load Diff
+677
@@ -0,0 +1,677 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
static void juceFreeAccessibilityPlatformSpecificData (UIAccessibilityElement* element)
|
||||
{
|
||||
if (auto* container = juce::getIvar<UIAccessibilityElement*> (element, "container"))
|
||||
{
|
||||
object_setInstanceVariable (element, "container", nullptr);
|
||||
object_setInstanceVariable (container, "handler", nullptr);
|
||||
|
||||
[container release];
|
||||
}
|
||||
}
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
|
||||
|
||||
template <typename> struct Signature {};
|
||||
|
||||
template <typename Result, typename... Args>
|
||||
struct Signature<Result (Args...)> {};
|
||||
|
||||
// @selector isn't constexpr, so the 'sel' members are functions rather than static constexpr data members
|
||||
struct SignatureHasText final : public Signature<BOOL()> { static auto sel() { return @selector (hasText); } };
|
||||
struct SignatureSetSelectedTextRange final : public Signature<void (UITextRange*)> { static auto sel() { return @selector (setSelectedTextRange:); } };
|
||||
struct SignatureSelectedTextRange final : public Signature<UITextRange*()> { static auto sel() { return @selector (selectedTextRange); } };
|
||||
struct SignatureMarkedTextRange final : public Signature<UITextRange*()> { static auto sel() { return @selector (markedTextRange); } };
|
||||
struct SignatureSetMarkedTextSelectedRange final : public Signature<void (NSString*, NSRange)> { static auto sel() { return @selector (setMarkedText:selectedRange:); } };
|
||||
struct SignatureUnmarkText final : public Signature<void()> { static auto sel() { return @selector (unmarkText); } };
|
||||
struct SignatureMarkedTextStyle final : public Signature<NSDictionary<NSAttributedStringKey, id>*()> { static auto sel() { return @selector (markedTextStyle); } };
|
||||
struct SignatureSetMarkedTextStyle final : public Signature<void (NSDictionary<NSAttributedStringKey, id>*)> { static auto sel() { return @selector (setMarkedTextStyle:); } };
|
||||
struct SignatureBeginningOfDocument final : public Signature<UITextPosition*()> { static auto sel() { return @selector (beginningOfDocument); } };
|
||||
struct SignatureEndOfDocument final : public Signature<UITextPosition*()> { static auto sel() { return @selector (endOfDocument); } };
|
||||
struct SignatureTokenizer final : public Signature<id<UITextInputTokenizer>()> { static auto sel() { return @selector (tokenizer); } };
|
||||
struct SignatureBaseWritingDirection final : public Signature<NSWritingDirection (UITextPosition*, UITextStorageDirection)> { static auto sel() { return @selector (baseWritingDirectionForPosition:inDirection:); } };
|
||||
struct SignatureCaretRectForPosition final : public Signature<CGRect (UITextPosition*)> { static auto sel() { return @selector (caretRectForPosition:); } };
|
||||
struct SignatureCharacterRangeByExtending final : public Signature<UITextRange* (UITextPosition*, UITextLayoutDirection)> { static auto sel() { return @selector (characterRangeByExtendingPosition:inDirection:); } };
|
||||
struct SignatureCharacterRangeAtPoint final : public Signature<UITextRange* (CGPoint)> { static auto sel() { return @selector (characterRangeAtPoint:); } };
|
||||
struct SignatureClosestPositionToPoint final : public Signature<UITextPosition* (CGPoint)> { static auto sel() { return @selector (closestPositionToPoint:); } };
|
||||
struct SignatureClosestPositionToPointInRange final : public Signature<UITextPosition* (CGPoint, UITextRange*)> { static auto sel() { return @selector (closestPositionToPoint:withinRange:); } };
|
||||
struct SignatureComparePositionToPosition final : public Signature<NSComparisonResult (UITextPosition*, UITextPosition*)> { static auto sel() { return @selector (comparePosition:toPosition:); } };
|
||||
struct SignatureOffsetFromPositionToPosition final : public Signature<NSInteger (UITextPosition*, UITextPosition*)> { static auto sel() { return @selector (offsetFromPosition:toPosition:); } };
|
||||
struct SignaturePositionFromPositionInDirection final : public Signature<UITextPosition* (UITextPosition*, UITextLayoutDirection, NSInteger)> { static auto sel() { return @selector (positionFromPosition:inDirection:offset:); } };
|
||||
struct SignaturePositionFromPositionOffset final : public Signature<UITextPosition* (UITextPosition*, NSInteger)> { static auto sel() { return @selector (positionFromPosition:offset:); } };
|
||||
struct SignatureFirstRectForRange final : public Signature<CGRect (UITextRange*)> { static auto sel() { return @selector (firstRectForRange:); } };
|
||||
struct SignatureSelectionRectsForRange final : public Signature<NSArray<UITextSelectionRect*>* (UITextRange*)> { static auto sel() { return @selector (selectionRectsForRange:); } };
|
||||
struct SignaturePositionWithinRange final : public Signature<UITextPosition* (UITextRange*, UITextLayoutDirection)> { static auto sel() { return @selector (positionWithinRange:farthestInDirection:); } };
|
||||
struct SignatureReplaceRangeWithText final : public Signature<void (UITextRange*, NSString*)> { static auto sel() { return @selector (replaceRange:withText:); } };
|
||||
struct SignatureSetBaseWritingDirection final : public Signature<void (NSWritingDirection, UITextRange*)> { static auto sel() { return @selector (setBaseWritingDirection:forRange:); } };
|
||||
struct SignatureTextInRange final : public Signature<NSString* (UITextRange*)> { static auto sel() { return @selector (textInRange:); } };
|
||||
struct SignatureTextRangeFromPosition final : public Signature<UITextRange* (UITextPosition*, UITextPosition*)> { static auto sel() { return @selector (textRangeFromPosition:toPosition:); } };
|
||||
struct SignatureSetInputDelegate final : public Signature<void (id)> { static auto sel() { return @selector (setInputDelegate:); } };
|
||||
struct SignatureInputDelegate final : public Signature<id()> { static auto sel() { return @selector (inputDelegate); } };
|
||||
struct SignatureKeyboardType final : public Signature<UIKeyboardType()> { static auto sel() { return @selector (keyboardType); } };
|
||||
struct SignatureAutocapitalizationType final : public Signature<UITextAutocapitalizationType()> { static auto sel() { return @selector (autocapitalizationType); } };
|
||||
struct SignatureAutocorrectionType final : public Signature<UITextAutocorrectionType()> { static auto sel() { return @selector (autocorrectionType); } };
|
||||
|
||||
//==============================================================================
|
||||
class AccessibilityHandler::AccessibilityNativeImpl
|
||||
{
|
||||
public:
|
||||
explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
|
||||
: accessibilityElement (AccessibilityElement::create (handler))
|
||||
{
|
||||
}
|
||||
|
||||
UIAccessibilityElement* getAccessibilityElement() const noexcept
|
||||
{
|
||||
return accessibilityElement.get();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class AccessibilityContainer final : public AccessibleObjCClass<NSObject>
|
||||
{
|
||||
public:
|
||||
AccessibilityContainer()
|
||||
: AccessibleObjCClass ("JUCEUIAccessibilityContainer_")
|
||||
{
|
||||
addMethod (@selector (isAccessibilityElement), [] (id, SEL) { return false; });
|
||||
|
||||
addMethod (@selector (accessibilityFrame), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return convertToCGRect (handler->getComponent().getScreenBounds());
|
||||
|
||||
return CGRectZero;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityElements), [] (id self, SEL) -> NSArray*
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return getContainerAccessibilityElements (*handler);
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
if (@available (iOS 11.0, *))
|
||||
{
|
||||
addMethod (@selector (accessibilityDataTableCellElementForRow:column:), [] (id self, SEL, NSUInteger row, NSUInteger column) -> id
|
||||
{
|
||||
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
|
||||
if (auto* tableInterface = tableHandler->getTableInterface())
|
||||
if (auto* cellHandler = tableInterface->getCellHandler ((int) row, (int) column))
|
||||
if (auto* parent = getAccessibleParent (cellHandler))
|
||||
return static_cast<id> (parent->getNativeImplementation());
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount);
|
||||
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount);
|
||||
|
||||
addMethod (@selector (accessibilityHeaderElementsForColumn:), [] (id self, SEL, NSUInteger column) -> NSArray*
|
||||
{
|
||||
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (getHandler (self), &AccessibilityHandler::getTableInterface))
|
||||
{
|
||||
if (auto* tableInterface = tableHandler->getTableInterface())
|
||||
{
|
||||
if (auto* header = tableInterface->getHeaderHandler())
|
||||
{
|
||||
if (isPositiveAndBelow (column, header->getChildren().size()))
|
||||
{
|
||||
auto* result = [NSMutableArray new];
|
||||
[result addObject: static_cast<id> (header->getChildren()[(size_t) column]->getNativeImplementation())];
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
addProtocol (@protocol (UIAccessibilityContainerDataTable));
|
||||
|
||||
addMethod (@selector (accessibilityContainerType), [] (id self, SEL) -> NSInteger
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (handler->getTableInterface() != nullptr)
|
||||
{
|
||||
if (@available (iOS 11.0, *))
|
||||
return UIAccessibilityContainerTypeDataTable;
|
||||
|
||||
return 1; // UIAccessibilityContainerTypeDataTable
|
||||
}
|
||||
|
||||
const auto handlerRole = handler->getRole();
|
||||
|
||||
if (handlerRole == AccessibilityRole::popupMenu
|
||||
|| handlerRole == AccessibilityRole::list
|
||||
|| handlerRole == AccessibilityRole::tree)
|
||||
{
|
||||
if (@available (iOS 11.0, *))
|
||||
return UIAccessibilityContainerTypeList;
|
||||
|
||||
return 2; // UIAccessibilityContainerTypeList
|
||||
}
|
||||
}
|
||||
|
||||
if (@available (iOS 11.0, *))
|
||||
return UIAccessibilityContainerTypeNone;
|
||||
|
||||
return 0; // UIAccessibilityContainerTypeNone
|
||||
});
|
||||
}
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static const AccessibilityHandler* getAccessibleParent (const AccessibilityHandler* h)
|
||||
{
|
||||
if (h == nullptr)
|
||||
return nullptr;
|
||||
|
||||
if ([static_cast<id> (h->getNativeImplementation()) isAccessibilityElement])
|
||||
return h;
|
||||
|
||||
return getAccessibleParent (h->getParent());
|
||||
}
|
||||
|
||||
static AccessibilityHandler* getHandler (id self)
|
||||
{
|
||||
return getIvar<AccessibilityHandler*> (self, "handler");
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AccessibilityElement final : public AccessibleObjCClass<UIAccessibilityElement>
|
||||
{
|
||||
template <typename Func, typename... Items>
|
||||
static constexpr void forEach (Func&& func, Items&&... items)
|
||||
{
|
||||
(func (std::forward<Items> (items)), ...);
|
||||
}
|
||||
|
||||
public:
|
||||
enum class Type { defaultElement, textElement };
|
||||
|
||||
static Holder create (AccessibilityHandler& handler)
|
||||
{
|
||||
static AccessibilityElement cls { Type::defaultElement };
|
||||
static AccessibilityElement textCls { Type::textElement };
|
||||
|
||||
id instance = (hasEditableText (handler) ? textCls : cls).createInstance();
|
||||
|
||||
Holder element ([instance initWithAccessibilityContainer: static_cast<id> (handler.getComponent().getWindowHandle())]);
|
||||
object_setInstanceVariable (element.get(), "handler", &handler);
|
||||
return element;
|
||||
}
|
||||
|
||||
AccessibilityElement (Type elementType)
|
||||
{
|
||||
addMethod (@selector (isAccessibilityElement), [] (id self, SEL)
|
||||
{
|
||||
auto* handler = getHandler (self);
|
||||
|
||||
const auto hasAccessiblePropertiesOrIsTableCell = [] (auto& handlerRef)
|
||||
{
|
||||
const auto isTableCell = [&]
|
||||
{
|
||||
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&handlerRef, &AccessibilityHandler::getTableInterface))
|
||||
{
|
||||
if (auto* tableInterface = tableHandler->getTableInterface())
|
||||
{
|
||||
return tableInterface->getRowSpan (handlerRef).hasValue()
|
||||
&& tableInterface->getColumnSpan (handlerRef).hasValue();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
return handlerRef.getTitle().isNotEmpty()
|
||||
|| handlerRef.getHelp().isNotEmpty()
|
||||
|| handlerRef.getTextInterface() != nullptr
|
||||
|| handlerRef.getValueInterface() != nullptr
|
||||
|| isTableCell();
|
||||
};
|
||||
|
||||
return handler != nullptr
|
||||
&& ! handler->isIgnored()
|
||||
&& handler->getRole() != AccessibilityRole::window
|
||||
&& hasAccessiblePropertiesOrIsTableCell (*handler);
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityContainer), [] (id self, SEL) -> id
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (handler->getComponent().isOnDesktop())
|
||||
return static_cast<id> (handler->getComponent().getWindowHandle());
|
||||
|
||||
if ( ! handler->getChildren().empty()
|
||||
|| AccessibilityHandler::getNativeChildForComponent (handler->getComponent()) != nullptr)
|
||||
{
|
||||
if (UIAccessibilityElement* container = getContainer (self))
|
||||
return container;
|
||||
|
||||
static AccessibilityContainer cls;
|
||||
|
||||
id container = cls.createInstance();
|
||||
|
||||
object_setInstanceVariable (container, "handler", handler);
|
||||
object_setInstanceVariable (self, "container", container);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
if (auto* parent = handler->getParent())
|
||||
return [static_cast<id> (parent->getNativeImplementation()) accessibilityContainer];
|
||||
}
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityFrame), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return convertToCGRect (handler->getComponent().getScreenBounds());
|
||||
|
||||
return CGRectZero;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityTraits), [] (id self, SEL)
|
||||
{
|
||||
auto traits = UIAccessibilityTraits{};
|
||||
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
traits |= [&handler]
|
||||
{
|
||||
switch (handler->getRole())
|
||||
{
|
||||
case AccessibilityRole::button:
|
||||
case AccessibilityRole::toggleButton:
|
||||
case AccessibilityRole::radioButton:
|
||||
case AccessibilityRole::comboBox: return UIAccessibilityTraitButton;
|
||||
|
||||
case AccessibilityRole::label:
|
||||
case AccessibilityRole::staticText: return UIAccessibilityTraitStaticText;
|
||||
|
||||
case AccessibilityRole::image: return UIAccessibilityTraitImage;
|
||||
case AccessibilityRole::tableHeader: return UIAccessibilityTraitHeader;
|
||||
case AccessibilityRole::hyperlink: return UIAccessibilityTraitLink;
|
||||
case AccessibilityRole::ignored: return UIAccessibilityTraitNotEnabled;
|
||||
|
||||
case AccessibilityRole::editableText: return UIAccessibilityTraitKeyboardKey;
|
||||
|
||||
case AccessibilityRole::slider:
|
||||
case AccessibilityRole::menuItem:
|
||||
case AccessibilityRole::menuBar:
|
||||
case AccessibilityRole::popupMenu:
|
||||
case AccessibilityRole::table:
|
||||
case AccessibilityRole::column:
|
||||
case AccessibilityRole::row:
|
||||
case AccessibilityRole::cell:
|
||||
case AccessibilityRole::list:
|
||||
case AccessibilityRole::listItem:
|
||||
case AccessibilityRole::tree:
|
||||
case AccessibilityRole::treeItem:
|
||||
case AccessibilityRole::progressBar:
|
||||
case AccessibilityRole::group:
|
||||
case AccessibilityRole::dialogWindow:
|
||||
case AccessibilityRole::window:
|
||||
case AccessibilityRole::scrollBar:
|
||||
case AccessibilityRole::tooltip:
|
||||
case AccessibilityRole::splashScreen:
|
||||
case AccessibilityRole::unspecified: break;
|
||||
}
|
||||
|
||||
return UIAccessibilityTraitNone;
|
||||
}();
|
||||
|
||||
const auto state = handler->getCurrentState();
|
||||
|
||||
if (state.isSelected() || state.isChecked())
|
||||
traits |= UIAccessibilityTraitSelected;
|
||||
|
||||
if (auto* valueInterface = getValueInterface (self))
|
||||
if (! valueInterface->isReadOnly() && valueInterface->getRange().isValid())
|
||||
traits |= UIAccessibilityTraitAdjustable;
|
||||
}
|
||||
|
||||
return traits | sendSuperclassMessage<UIAccessibilityTraits> (self, @selector (accessibilityTraits));
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityLabel), getAccessibilityTitle);
|
||||
addMethod (@selector (accessibilityHint), getAccessibilityHelp);
|
||||
|
||||
addMethod (@selector (accessibilityValue), [] (id self, SEL) -> NSString*
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (handler->getCurrentState().isCheckable())
|
||||
return handler->getCurrentState().isChecked() ? @"1" : @"0";
|
||||
|
||||
return (NSString*) getAccessibilityValueFromInterfaces (*handler);
|
||||
}
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (setAccessibilityValue:), setAccessibilityValue);
|
||||
|
||||
addMethod (@selector (accessibilityElementDidBecomeFocused), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
const WeakReference<Component> safeComponent (&handler->getComponent());
|
||||
|
||||
performActionIfSupported (self, AccessibilityActionType::focus);
|
||||
|
||||
if (safeComponent != nullptr)
|
||||
handler->grabFocus();
|
||||
}
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityElementDidLoseFocus), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
handler->giveAwayFocus();
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityElementIsFocused), [] (id self, SEL) -> BOOL
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return handler->hasFocus (false);
|
||||
|
||||
return NO;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityViewIsModal), getIsAccessibilityModal);
|
||||
|
||||
addMethod (@selector (accessibilityActivate), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
// Occasionally VoiceOver sends accessibilityActivate to the wrong element, so we first query
|
||||
// which element it thinks has focus and forward the event on to that element if it differs
|
||||
id focusedElement = UIAccessibilityFocusedElement (UIAccessibilityNotificationVoiceOverIdentifier);
|
||||
|
||||
if (focusedElement != nullptr && ! [static_cast<id> (handler->getNativeImplementation()) isEqual: focusedElement])
|
||||
return [focusedElement accessibilityActivate];
|
||||
|
||||
if (handler->hasFocus (false))
|
||||
return accessibilityPerformPress (self, {});
|
||||
}
|
||||
|
||||
return NO;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityIncrement), accessibilityPerformIncrement);
|
||||
addMethod (@selector (accessibilityDecrement), accessibilityPerformDecrement);
|
||||
|
||||
addMethod (@selector (accessibilityPerformEscape), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* modal = Component::getCurrentlyModalComponent())
|
||||
{
|
||||
if (auto* modalHandler = modal->getAccessibilityHandler())
|
||||
{
|
||||
if (modalHandler == handler || modalHandler->isParentOf (handler))
|
||||
{
|
||||
modal->exitModalState (0);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
});
|
||||
|
||||
if (elementType == Type::textElement)
|
||||
{
|
||||
addMethod (@selector (deleteBackward), [] (id, SEL) {});
|
||||
addMethod (@selector (insertText:), [] (id, SEL, NSString*) {});
|
||||
|
||||
forEach ([this] (auto signature) { addPassthroughMethodWithSignature (signature); },
|
||||
SignatureHasText{},
|
||||
SignatureSetSelectedTextRange{},
|
||||
SignatureSelectedTextRange{},
|
||||
SignatureMarkedTextRange{},
|
||||
SignatureSetMarkedTextSelectedRange{},
|
||||
SignatureUnmarkText{},
|
||||
SignatureMarkedTextStyle{},
|
||||
SignatureSetMarkedTextStyle{},
|
||||
SignatureBeginningOfDocument{},
|
||||
SignatureEndOfDocument{},
|
||||
SignatureTokenizer{},
|
||||
SignatureBaseWritingDirection{},
|
||||
SignatureCaretRectForPosition{},
|
||||
SignatureCharacterRangeByExtending{},
|
||||
SignatureCharacterRangeAtPoint{},
|
||||
SignatureClosestPositionToPoint{},
|
||||
SignatureClosestPositionToPointInRange{},
|
||||
SignatureComparePositionToPosition{},
|
||||
SignatureOffsetFromPositionToPosition{},
|
||||
SignaturePositionFromPositionInDirection{},
|
||||
SignaturePositionFromPositionOffset{},
|
||||
SignatureFirstRectForRange{},
|
||||
SignatureSelectionRectsForRange{},
|
||||
SignaturePositionWithinRange{},
|
||||
SignatureReplaceRangeWithText{},
|
||||
SignatureSetBaseWritingDirection{},
|
||||
SignatureTextInRange{},
|
||||
SignatureTextRangeFromPosition{},
|
||||
SignatureSetInputDelegate{},
|
||||
SignatureInputDelegate{},
|
||||
SignatureKeyboardType{},
|
||||
SignatureAutocapitalizationType{},
|
||||
SignatureAutocorrectionType{});
|
||||
|
||||
addProtocol (@protocol (UITextInput));
|
||||
}
|
||||
|
||||
if (@available (iOS 11.0, *))
|
||||
{
|
||||
addMethod (@selector (accessibilityRowRange), getAccessibilityRowIndexRange);
|
||||
addMethod (@selector (accessibilityColumnRange), getAccessibilityColumnIndexRange);
|
||||
addProtocol (@protocol (UIAccessibilityContainerDataTableCell));
|
||||
}
|
||||
|
||||
addIvar<UIAccessibilityElement*> ("container");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Result>
|
||||
static auto getResult (NSInvocation* invocation, detail::Tag<Result>)
|
||||
{
|
||||
Result result{};
|
||||
[invocation getReturnValue: &result];
|
||||
return result;
|
||||
}
|
||||
|
||||
static void getResult (NSInvocation*, detail::Tag<void>) {}
|
||||
|
||||
template <typename HasSelector, typename Result, typename... Args>
|
||||
auto makePassthroughCallback (HasSelector, Signature<Result (Args...)>)
|
||||
{
|
||||
return [] (id self, SEL, Args... args) -> Result
|
||||
{
|
||||
if (auto* input = getPeerTextInput (self))
|
||||
{
|
||||
const auto s = detail::makeCompileTimeStr (@encode (Result), @encode (id), @encode (SEL), @encode (Args)...);
|
||||
const auto signature = [NSMethodSignature signatureWithObjCTypes: s.data()];
|
||||
|
||||
if (signature == nullptr)
|
||||
{
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion")
|
||||
const auto invocation = [NSInvocation invocationWithMethodSignature: signature];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
invocation.selector = HasSelector::sel();
|
||||
|
||||
// Indices 0 and 1 are 'id self' and 'SEL _cmd' respectively
|
||||
auto counter = 2;
|
||||
forEach ([&] (auto& arg) { [invocation setArgument: &arg atIndex: counter++]; }, args...);
|
||||
|
||||
[invocation invokeWithTarget: input];
|
||||
|
||||
return getResult (invocation, detail::Tag<Result>{});
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Signature>
|
||||
void addPassthroughMethodWithSignature (Signature signature)
|
||||
{
|
||||
addMethod (Signature::sel(), makePassthroughCallback (signature, signature));
|
||||
}
|
||||
|
||||
static UIAccessibilityElement* getContainer (id self)
|
||||
{
|
||||
return getIvar<UIAccessibilityElement*> (self, "container");
|
||||
}
|
||||
|
||||
static UIViewComponentPeer* getPeer (id self)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return static_cast<UIViewComponentPeer*> (handler->getComponent().getPeer());
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static JuceTextView* getPeerTextInput (id self)
|
||||
{
|
||||
if (auto* peer = getPeer (self))
|
||||
return peer->hiddenTextInput.get();
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AccessibilityElement::Holder accessibilityElement;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
|
||||
{
|
||||
return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement();
|
||||
}
|
||||
|
||||
static bool areAnyAccessibilityClientsActive()
|
||||
{
|
||||
return UIAccessibilityIsVoiceOverRunning();
|
||||
}
|
||||
|
||||
static void sendAccessibilityEvent (UIAccessibilityNotifications notification, id argument)
|
||||
{
|
||||
if (! areAnyAccessibilityClientsActive())
|
||||
return;
|
||||
|
||||
jassert (notification != UIAccessibilityNotifications{});
|
||||
|
||||
UIAccessibilityPostNotification (notification, argument);
|
||||
}
|
||||
|
||||
void detail::AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler& handler, Event eventType)
|
||||
{
|
||||
auto notification = [eventType]
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case Event::elementCreated:
|
||||
case Event::elementDestroyed:
|
||||
case Event::elementMovedOrResized:
|
||||
case Event::focusChanged: return UIAccessibilityLayoutChangedNotification;
|
||||
|
||||
case Event::windowOpened:
|
||||
case Event::windowClosed: return UIAccessibilityScreenChangedNotification;
|
||||
}
|
||||
|
||||
return UIAccessibilityNotifications{};
|
||||
}();
|
||||
|
||||
if (notification != UIAccessibilityNotifications{})
|
||||
{
|
||||
const bool moveToHandler = (eventType == Event::focusChanged && handler.hasFocus (false));
|
||||
|
||||
sendAccessibilityEvent (notification,
|
||||
moveToHandler ? static_cast<id> (handler.getNativeImplementation()) : nil);
|
||||
}
|
||||
}
|
||||
|
||||
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
|
||||
{
|
||||
auto notification = [eventType]
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case AccessibilityEvent::textSelectionChanged:
|
||||
case AccessibilityEvent::rowSelectionChanged:
|
||||
case AccessibilityEvent::textChanged:
|
||||
case AccessibilityEvent::valueChanged:
|
||||
case AccessibilityEvent::titleChanged: break;
|
||||
|
||||
case AccessibilityEvent::structureChanged: return UIAccessibilityLayoutChangedNotification;
|
||||
}
|
||||
|
||||
return UIAccessibilityNotifications{};
|
||||
}();
|
||||
|
||||
if (notification != UIAccessibilityNotifications{})
|
||||
sendAccessibilityEvent (notification, static_cast<id> (getNativeImplementation()));
|
||||
}
|
||||
|
||||
void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority)
|
||||
{
|
||||
sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+960
@@ -0,0 +1,960 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
API_AVAILABLE (macos (10.10))
|
||||
static void juceFreeAccessibilityPlatformSpecificData (NSAccessibilityElement<NSAccessibility>*) {}
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
|
||||
|
||||
//==============================================================================
|
||||
class AccessibilityHandler::AccessibilityNativeImpl
|
||||
{
|
||||
public:
|
||||
explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
|
||||
{
|
||||
if (@available (macOS 10.10, *))
|
||||
accessibilityElement = AccessibilityElement::create (handler);
|
||||
}
|
||||
|
||||
API_AVAILABLE (macos (10.10))
|
||||
NSAccessibilityElement<NSAccessibility>* getAccessibilityElement() const noexcept
|
||||
{
|
||||
return accessibilityElement.get();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class API_AVAILABLE (macos (10.10)) AccessibilityElement final : public AccessibleObjCClass<NSAccessibilityElement<NSAccessibility>>
|
||||
{
|
||||
public:
|
||||
static Holder create (AccessibilityHandler& handler)
|
||||
{
|
||||
if (@available (macOS 10.10, *))
|
||||
{
|
||||
static AccessibilityElement cls;
|
||||
Holder element ([cls.createInstance() init]);
|
||||
object_setInstanceVariable (element.get(), "handler", &handler);
|
||||
return element;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
AccessibilityElement()
|
||||
{
|
||||
addMethod (@selector (accessibilityNotifiesWhenDestroyed), [] (id, SEL) { return YES; });
|
||||
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement);
|
||||
|
||||
addMethod (@selector (isAccessibilityEnabled), [] (id self, SEL) -> BOOL
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return handler->getComponent().isEnabled();
|
||||
|
||||
return NO;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityWindow), getAccessibilityWindow);
|
||||
addMethod (@selector (accessibilityTopLevelUIElement), getAccessibilityWindow);
|
||||
addMethod (@selector (accessibilityChildren), getAccessibilityChildren);
|
||||
addMethod (@selector (isAccessibilityModal), getIsAccessibilityModal);
|
||||
|
||||
addMethod (@selector (accessibilityFocusedUIElement), [] (id self, SEL) -> id
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* modal = Component::getCurrentlyModalComponent())
|
||||
{
|
||||
const auto& handlerComponent = handler->getComponent();
|
||||
|
||||
if (! handlerComponent.isParentOf (modal)
|
||||
&& handlerComponent.isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if (auto* modalHandler = modal->getAccessibilityHandler())
|
||||
{
|
||||
if (auto* focusChild = modalHandler->getChildFocus())
|
||||
return static_cast<id> (focusChild->getNativeImplementation());
|
||||
|
||||
return static_cast<id> (modalHandler->getNativeImplementation());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* focusChild = handler->getChildFocus())
|
||||
return static_cast<id> (focusChild->getNativeImplementation());
|
||||
}
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityHitTest:), [] (id self, SEL, NSPoint point) -> id
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* child = handler->getChildAt (roundToIntPoint (flippedScreenPoint (point))))
|
||||
return static_cast<id> (child->getNativeImplementation());
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityParent), [] (id self, SEL) -> id
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* parentHandler = handler->getParent())
|
||||
return NSAccessibilityUnignoredAncestor (static_cast<id> (parentHandler->getNativeImplementation()));
|
||||
|
||||
return NSAccessibilityUnignoredAncestor (static_cast<id> (handler->getComponent().getWindowHandle()));
|
||||
}
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (isAccessibilityFocused), [] (id self, SEL)
|
||||
{
|
||||
return [[self accessibilityWindow] accessibilityFocusedUIElement] == self;
|
||||
});
|
||||
|
||||
addMethod (@selector (setAccessibilityFocused:), [] (id self, SEL, BOOL focused)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (focused)
|
||||
{
|
||||
const WeakReference<Component> safeComponent (&handler->getComponent());
|
||||
|
||||
performActionIfSupported (self, AccessibilityActionType::focus);
|
||||
|
||||
if (safeComponent != nullptr)
|
||||
handler->grabFocus();
|
||||
}
|
||||
else
|
||||
{
|
||||
handler->giveAwayFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityFrame), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return flippedScreenRect (makeNSRect (handler->getComponent().getScreenBounds()));
|
||||
|
||||
return NSZeroRect;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityRole), [] (id self, SEL) -> NSAccessibilityRole
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
switch (handler->getRole())
|
||||
{
|
||||
case AccessibilityRole::popupMenu:
|
||||
case AccessibilityRole::tooltip:
|
||||
case AccessibilityRole::splashScreen:
|
||||
case AccessibilityRole::dialogWindow:
|
||||
case AccessibilityRole::window: return NSAccessibilityWindowRole;
|
||||
|
||||
case AccessibilityRole::tableHeader:
|
||||
case AccessibilityRole::unspecified:
|
||||
case AccessibilityRole::group: return NSAccessibilityGroupRole;
|
||||
|
||||
case AccessibilityRole::label:
|
||||
case AccessibilityRole::staticText: return NSAccessibilityStaticTextRole;
|
||||
|
||||
case AccessibilityRole::tree:
|
||||
case AccessibilityRole::list: return NSAccessibilityOutlineRole;
|
||||
|
||||
case AccessibilityRole::listItem:
|
||||
case AccessibilityRole::treeItem: return NSAccessibilityRowRole;
|
||||
|
||||
case AccessibilityRole::button: return NSAccessibilityButtonRole;
|
||||
case AccessibilityRole::toggleButton: return NSAccessibilityCheckBoxRole;
|
||||
case AccessibilityRole::radioButton: return NSAccessibilityRadioButtonRole;
|
||||
case AccessibilityRole::comboBox: return NSAccessibilityPopUpButtonRole;
|
||||
case AccessibilityRole::image: return NSAccessibilityImageRole;
|
||||
case AccessibilityRole::slider: return NSAccessibilitySliderRole;
|
||||
case AccessibilityRole::editableText: return NSAccessibilityTextAreaRole;
|
||||
case AccessibilityRole::menuItem: return NSAccessibilityMenuItemRole;
|
||||
case AccessibilityRole::menuBar: return NSAccessibilityMenuRole;
|
||||
case AccessibilityRole::table: return NSAccessibilityOutlineRole;
|
||||
case AccessibilityRole::column: return NSAccessibilityColumnRole;
|
||||
case AccessibilityRole::row: return NSAccessibilityRowRole;
|
||||
case AccessibilityRole::cell: return NSAccessibilityCellRole;
|
||||
case AccessibilityRole::hyperlink: return NSAccessibilityLinkRole;
|
||||
case AccessibilityRole::progressBar: return NSAccessibilityProgressIndicatorRole;
|
||||
case AccessibilityRole::scrollBar: return NSAccessibilityScrollBarRole;
|
||||
|
||||
case AccessibilityRole::ignored: break;
|
||||
}
|
||||
|
||||
return NSAccessibilityUnknownRole;
|
||||
}
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilitySubrole), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
if (textInterface->isDisplayingProtectedText())
|
||||
return NSAccessibilitySecureTextFieldSubrole;
|
||||
|
||||
const auto handlerRole = handler->getRole();
|
||||
|
||||
if (handlerRole == AccessibilityRole::window) return NSAccessibilityStandardWindowSubrole;
|
||||
if (handlerRole == AccessibilityRole::dialogWindow) return NSAccessibilityDialogSubrole;
|
||||
if (handlerRole == AccessibilityRole::tooltip
|
||||
|| handlerRole == AccessibilityRole::splashScreen) return NSAccessibilityFloatingWindowSubrole;
|
||||
if (handlerRole == AccessibilityRole::toggleButton) return NSAccessibilityToggleSubrole;
|
||||
if (handlerRole == AccessibilityRole::treeItem
|
||||
|| handlerRole == AccessibilityRole::listItem) return NSAccessibilityOutlineRowSubrole;
|
||||
if (handlerRole == AccessibilityRole::row && getCellInterface (self) != nullptr) return NSAccessibilityTableRowSubrole;
|
||||
|
||||
const auto& handlerComponent = handler->getComponent();
|
||||
|
||||
if (auto* documentWindow = handlerComponent.findParentComponentOfClass<DocumentWindow>())
|
||||
{
|
||||
if (handlerRole == AccessibilityRole::button)
|
||||
{
|
||||
if (&handlerComponent == documentWindow->getCloseButton()) return NSAccessibilityCloseButtonSubrole;
|
||||
if (&handlerComponent == documentWindow->getMinimiseButton()) return NSAccessibilityMinimizeButtonSubrole;
|
||||
if (&handlerComponent == documentWindow->getMaximiseButton()) return NSAccessibilityFullScreenButtonSubrole;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NSAccessibilityUnknownRole;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityLabel), [] (id self, SEL) -> NSString*
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return juceStringToNS (handler->getDescription());
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityValue), [] (id self, SEL) -> id
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (! handler->getCurrentState().isCheckable())
|
||||
return getAccessibilityValueFromInterfaces (*handler);
|
||||
|
||||
const auto checked = handler->getCurrentState().isChecked();
|
||||
|
||||
if ( handler->getRole() == AccessibilityRole::toggleButton
|
||||
|| handler->getRole() == AccessibilityRole::radioButton)
|
||||
{
|
||||
return checked ? @YES : @NO;
|
||||
}
|
||||
|
||||
return juceStringToNS (checked ? TRANS ("On") : TRANS ("Off"));
|
||||
}
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityTitle), getAccessibilityTitle);
|
||||
addMethod (@selector (accessibilityHelp), getAccessibilityHelp);
|
||||
addMethod (@selector (setAccessibilityValue:), setAccessibilityValue);
|
||||
|
||||
addMethod (@selector (accessibilitySelectedChildren), [] (id self, SEL)
|
||||
{
|
||||
return getSelectedChildren ([self accessibilityChildren]);
|
||||
});
|
||||
|
||||
addMethod (@selector (setAccessibilitySelectedChildren:), [] (id self, SEL, NSArray* selected)
|
||||
{
|
||||
setSelectedChildren ([self accessibilityChildren], selected);
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityOrientation), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return handler->getComponent().getBounds().toFloat().getAspectRatio() > 1.0f
|
||||
? NSAccessibilityOrientationHorizontal
|
||||
: NSAccessibilityOrientationVertical;
|
||||
|
||||
return NSAccessibilityOrientationUnknown;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityInsertionPointLineNumber), [] (id self, SEL) -> NSInteger
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return [self accessibilityLineForIndex: textInterface->getTextInsertionOffset()];
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityVisibleCharacterRange), [] (id self, SEL)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return juceRangeToNS ({ 0, textInterface->getTotalNumCharacters() });
|
||||
|
||||
return NSMakeRange (0, 0);
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityNumberOfCharacters), [] (id self, SEL)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return textInterface->getTotalNumCharacters();
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilitySelectedText), [] (id self, SEL) -> NSString*
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return juceStringToNS (textInterface->getText (textInterface->getSelection()));
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilitySelectedTextRange), [] (id self, SEL)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
{
|
||||
const auto currentSelection = textInterface->getSelection();
|
||||
|
||||
if (currentSelection.isEmpty())
|
||||
return NSMakeRange ((NSUInteger) textInterface->getTextInsertionOffset(), 0);
|
||||
|
||||
return juceRangeToNS (currentSelection);
|
||||
}
|
||||
|
||||
return NSMakeRange (0, 0);
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityAttributedStringForRange:), [] (id self, SEL, NSRange range) -> NSAttributedString*
|
||||
{
|
||||
NSString* string = [self accessibilityStringForRange: range];
|
||||
|
||||
if (string != nil)
|
||||
return [[[NSAttributedString alloc] initWithString: string] autorelease];
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityRangeForLine:), [] (id self, SEL, NSInteger line)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
{
|
||||
auto text = textInterface->getText ({ 0, textInterface->getTotalNumCharacters() });
|
||||
auto lines = StringArray::fromLines (text);
|
||||
|
||||
if (line < lines.size())
|
||||
{
|
||||
auto lineText = lines[(int) line];
|
||||
auto start = text.indexOf (lineText);
|
||||
|
||||
if (start >= 0)
|
||||
return NSMakeRange ((NSUInteger) start, (NSUInteger) lineText.length());
|
||||
}
|
||||
}
|
||||
|
||||
return NSMakeRange (0, 0);
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityStringForRange:), [] (id self, SEL, NSRange range) -> NSString*
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return juceStringToNS (textInterface->getText (nsRangeToJuce (range)));
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityRangeForPosition:), [] (id self, SEL, NSPoint position)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* textInterface = handler->getTextInterface())
|
||||
{
|
||||
auto screenPoint = roundToIntPoint (flippedScreenPoint (position));
|
||||
|
||||
if (handler->getComponent().getScreenBounds().contains (screenPoint))
|
||||
{
|
||||
auto offset = textInterface->getOffsetAtPoint (screenPoint);
|
||||
|
||||
if (offset >= 0)
|
||||
return NSMakeRange ((NSUInteger) offset, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NSMakeRange (0, 0);
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityRangeForIndex:), [] (id self, SEL, NSInteger index)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
if (isPositiveAndBelow (index, textInterface->getTotalNumCharacters()))
|
||||
return NSMakeRange ((NSUInteger) index, 1);
|
||||
|
||||
return NSMakeRange (0, 0);
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityFrameForRange:), [] (id self, SEL, NSRange range)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return flippedScreenRect (makeNSRect (textInterface->getTextBounds (nsRangeToJuce (range)).getBounds()));
|
||||
|
||||
return NSZeroRect;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityLineForIndex:), [] (id self, SEL, NSInteger index)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
{
|
||||
auto text = textInterface->getText ({ 0, (int) index });
|
||||
|
||||
if (! text.isEmpty())
|
||||
return StringArray::fromLines (text).size() - 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
addMethod (@selector (setAccessibilitySelectedTextRange:), [] (id self, SEL, NSRange selectedRange)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
textInterface->setSelection (nsRangeToJuce (selectedRange));
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityRows), [] (id self, SEL) -> NSArray*
|
||||
{
|
||||
if (auto* tableInterface = getTableInterface (self))
|
||||
{
|
||||
auto* rows = [[NSMutableArray new] autorelease];
|
||||
|
||||
for (int row = 0, numRows = tableInterface->getNumRows(); row < numRows; ++row)
|
||||
{
|
||||
if (auto* rowHandler = tableInterface->getRowHandler (row))
|
||||
{
|
||||
[rows addObject: static_cast<id> (rowHandler->getNativeImplementation())];
|
||||
}
|
||||
else
|
||||
{
|
||||
[rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
|
||||
frame: NSZeroRect
|
||||
label: @"Offscreen Row"
|
||||
parent: self]];
|
||||
}
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilitySelectedRows), [] (id self, SEL)
|
||||
{
|
||||
return getSelectedChildren ([self accessibilityRows]);
|
||||
});
|
||||
|
||||
addMethod (@selector (setAccessibilitySelectedRows:), [] (id self, SEL, NSArray* selected)
|
||||
{
|
||||
setSelectedChildren ([self accessibilityRows], selected);
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityHeader), [] (id self, SEL) -> id
|
||||
{
|
||||
if (auto* tableInterface = getTableInterface (self))
|
||||
if (auto* handler = tableInterface->getHeaderHandler())
|
||||
return static_cast<id> (handler->getNativeImplementation());
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount);
|
||||
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount);
|
||||
addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange);
|
||||
addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange);
|
||||
|
||||
addMethod (@selector (accessibilityIndex), [] (id self, SEL) -> NSInteger
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (handler, &AccessibilityHandler::getTableInterface))
|
||||
{
|
||||
if (auto* tableInterface = tableHandler->getTableInterface())
|
||||
{
|
||||
NSAccessibilityRole handlerRole = [self accessibilityRole];
|
||||
|
||||
if ([handlerRole isEqual: NSAccessibilityRowRole])
|
||||
if (const auto span = tableInterface->getRowSpan (*handler))
|
||||
return span->begin;
|
||||
|
||||
if ([handlerRole isEqual: NSAccessibilityColumnRole])
|
||||
if (const auto span = tableInterface->getColumnSpan (*handler))
|
||||
return span->begin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityDisclosureLevel), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
if (auto* cellInterface = handler->getCellInterface())
|
||||
return cellInterface->getDisclosureLevel();
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityDisclosedRows), [] (id self, SEL) -> id
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* cellInterface = handler->getCellInterface())
|
||||
{
|
||||
const auto rows = cellInterface->getDisclosedRows();
|
||||
auto* result = [NSMutableArray arrayWithCapacity: rows.size()];
|
||||
|
||||
for (const auto& row : rows)
|
||||
{
|
||||
if (row != nullptr)
|
||||
[result addObject: static_cast<id> (row->getNativeImplementation())];
|
||||
else
|
||||
[result addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
|
||||
frame: NSZeroRect
|
||||
label: @"Offscreen Row"
|
||||
parent: self]];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
});
|
||||
|
||||
addMethod (@selector (isAccessibilityExpanded), [] (id self, SEL) -> BOOL
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return handler->getCurrentState().isExpanded();
|
||||
|
||||
return NO;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityPerformIncrement), accessibilityPerformIncrement);
|
||||
addMethod (@selector (accessibilityPerformDecrement), accessibilityPerformDecrement);
|
||||
addMethod (@selector (accessibilityPerformDelete), [] (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (hasEditableText (*handler))
|
||||
{
|
||||
handler->getTextInterface()->setText ({});
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (auto* valueInterface = handler->getValueInterface())
|
||||
{
|
||||
if (! valueInterface->isReadOnly())
|
||||
{
|
||||
valueInterface->setValue ({});
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityPerformPress), accessibilityPerformPress);
|
||||
|
||||
addMethod (@selector (accessibilityPerformShowMenu), [] (id self, SEL)
|
||||
{
|
||||
return performActionIfSupported (self, AccessibilityActionType::showMenu);
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityPerformRaise), [] (id self, SEL)
|
||||
{
|
||||
[self setAccessibilityFocused: YES]; return YES;
|
||||
});
|
||||
|
||||
addMethod (@selector (isAccessibilitySelectorAllowed:), [] (id self, SEL, SEL selector) -> BOOL
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
const auto handlerRole = handler->getRole();
|
||||
const auto currentState = handler->getCurrentState();
|
||||
|
||||
for (auto textSelector : { @selector (accessibilityInsertionPointLineNumber),
|
||||
@selector (accessibilityVisibleCharacterRange),
|
||||
@selector (accessibilityNumberOfCharacters),
|
||||
@selector (accessibilitySelectedText),
|
||||
@selector (accessibilitySelectedTextRange),
|
||||
@selector (accessibilityAttributedStringForRange:),
|
||||
@selector (accessibilityRangeForLine:),
|
||||
@selector (accessibilityStringForRange:),
|
||||
@selector (accessibilityRangeForPosition:),
|
||||
@selector (accessibilityRangeForIndex:),
|
||||
@selector (accessibilityFrameForRange:),
|
||||
@selector (accessibilityLineForIndex:),
|
||||
@selector (setAccessibilitySelectedTextRange:) })
|
||||
{
|
||||
if (selector == textSelector)
|
||||
return handler->getTextInterface() != nullptr;
|
||||
}
|
||||
|
||||
for (auto tableSelector : { @selector (accessibilityRowCount),
|
||||
@selector (accessibilityRows),
|
||||
@selector (accessibilitySelectedRows),
|
||||
@selector (accessibilityColumnCount),
|
||||
@selector (accessibilityHeader) })
|
||||
{
|
||||
if (selector == tableSelector)
|
||||
return handler->getTableInterface() != nullptr;
|
||||
}
|
||||
|
||||
for (auto cellSelector : { @selector (accessibilityRowIndexRange),
|
||||
@selector (accessibilityColumnIndexRange),
|
||||
@selector (accessibilityIndex),
|
||||
@selector (accessibilityDisclosureLevel) })
|
||||
{
|
||||
if (selector == cellSelector)
|
||||
return handler->getCellInterface() != nullptr;
|
||||
}
|
||||
|
||||
for (auto valueSelector : { @selector (accessibilityValue),
|
||||
@selector (setAccessibilityValue:),
|
||||
@selector (accessibilityPerformDelete),
|
||||
@selector (accessibilityPerformIncrement),
|
||||
@selector (accessibilityPerformDecrement) })
|
||||
{
|
||||
if (selector != valueSelector)
|
||||
continue;
|
||||
|
||||
auto* valueInterface = handler->getValueInterface();
|
||||
|
||||
if (selector == @selector (accessibilityValue))
|
||||
return valueInterface != nullptr
|
||||
|| hasEditableText (*handler)
|
||||
|| currentState.isCheckable();
|
||||
|
||||
auto hasEditableValue = [valueInterface] { return valueInterface != nullptr && ! valueInterface->isReadOnly(); };
|
||||
|
||||
if (selector == @selector (setAccessibilityValue:)
|
||||
|| selector == @selector (accessibilityPerformDelete))
|
||||
return hasEditableValue() || hasEditableText (*handler);
|
||||
|
||||
auto isRanged = [valueInterface] { return valueInterface != nullptr && valueInterface->getRange().isValid(); };
|
||||
|
||||
if (selector == @selector (accessibilityPerformIncrement)
|
||||
|| selector == @selector (accessibilityPerformDecrement))
|
||||
return hasEditableValue() && isRanged();
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
for (auto actionSelector : { @selector (accessibilityPerformPress),
|
||||
@selector (accessibilityPerformShowMenu),
|
||||
@selector (accessibilityPerformRaise),
|
||||
@selector (setAccessibilityFocused:) })
|
||||
{
|
||||
if (selector != actionSelector)
|
||||
continue;
|
||||
|
||||
if (selector == @selector (accessibilityPerformPress))
|
||||
return (handler->getCurrentState().isCheckable() && handler->getActions().contains (AccessibilityActionType::toggle))
|
||||
|| handler->getActions().contains (AccessibilityActionType::press);
|
||||
|
||||
if (selector == @selector (accessibilityPerformShowMenu))
|
||||
return handler->getActions().contains (AccessibilityActionType::showMenu);
|
||||
|
||||
if (selector == @selector (accessibilityPerformRaise))
|
||||
return [[self accessibilityRole] isEqual: NSAccessibilityWindowRole];
|
||||
|
||||
if (selector == @selector (setAccessibilityFocused:))
|
||||
return currentState.isFocusable();
|
||||
}
|
||||
|
||||
if (selector == @selector (accessibilitySelectedChildren))
|
||||
return handlerRole == AccessibilityRole::popupMenu;
|
||||
|
||||
if (selector == @selector (accessibilityOrientation))
|
||||
return handlerRole == AccessibilityRole::scrollBar;
|
||||
|
||||
if (selector == @selector (isAccessibilityExpanded))
|
||||
return currentState.isExpandable();
|
||||
|
||||
return sendSuperclassMessage<BOOL> (self, @selector (isAccessibilitySelectorAllowed:), selector);
|
||||
}
|
||||
|
||||
return NO;
|
||||
});
|
||||
|
||||
addMethod (@selector (accessibilityChildrenInNavigationOrder), getAccessibilityChildren);
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static bool isSelectable (AccessibleState state) noexcept
|
||||
{
|
||||
return state.isSelectable() || state.isMultiSelectable();
|
||||
}
|
||||
|
||||
static NSArray* getSelectedChildren (NSArray* children)
|
||||
{
|
||||
NSMutableArray* selected = [[NSMutableArray new] autorelease];
|
||||
|
||||
for (id child in children)
|
||||
{
|
||||
if (auto* handler = getHandler (child))
|
||||
{
|
||||
const auto currentState = handler->getCurrentState();
|
||||
|
||||
if (isSelectable (currentState) && currentState.isSelected())
|
||||
[selected addObject: child];
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
static void setSelected (id item, bool selected)
|
||||
{
|
||||
auto* handler = getHandler (item);
|
||||
|
||||
if (handler == nullptr)
|
||||
return;
|
||||
|
||||
const auto currentState = handler->getCurrentState();
|
||||
|
||||
if (isSelectable (currentState))
|
||||
{
|
||||
if (currentState.isSelected() != selected)
|
||||
handler->getActions().invoke (AccessibilityActionType::toggle);
|
||||
}
|
||||
else if (currentState.isFocusable())
|
||||
{
|
||||
[item setAccessibilityFocused: selected];
|
||||
}
|
||||
}
|
||||
|
||||
static void setSelectedChildren (NSArray* children, NSArray* selected)
|
||||
{
|
||||
for (id child in children)
|
||||
setSelected (child, [selected containsObject: child]);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static id getAccessibilityWindow (id self, SEL)
|
||||
{
|
||||
return [[self accessibilityParent] accessibilityWindow];
|
||||
}
|
||||
|
||||
static NSArray* getAccessibilityChildren (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
auto children = handler->getChildren();
|
||||
|
||||
auto* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
|
||||
|
||||
for (auto* childHandler : children)
|
||||
[accessibleChildren addObject: static_cast<id> (childHandler->getNativeImplementation())];
|
||||
|
||||
if (id nativeChild = static_cast<id> (AccessibilityHandler::getNativeChildForComponent (handler->getComponent())))
|
||||
{
|
||||
// Having both native and non-native children would require implementing an
|
||||
// ordering. However, this situation doesn't occur with any of our current
|
||||
// use-cases.
|
||||
jassert ([accessibleChildren count] == 0);
|
||||
|
||||
if ([nativeChild isAccessibilityElement])
|
||||
[accessibleChildren addObject:nativeChild];
|
||||
else if (auto* childrenOfChild = [nativeChild accessibilityChildren]; childrenOfChild != nil)
|
||||
[accessibleChildren addObjectsFromArray:(NSArray* _Nonnull) childrenOfChild];
|
||||
}
|
||||
|
||||
return accessibleChildren;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityElement)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
API_AVAILABLE (macos (10.10))
|
||||
AccessibilityElement::Holder accessibilityElement;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
|
||||
{
|
||||
if (@available (macOS 10.10, *))
|
||||
return (AccessibilityNativeHandle*) nativeImpl->getAccessibilityElement();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool areAnyAccessibilityClientsActive()
|
||||
{
|
||||
const String voiceOverKeyString ("voiceOverOnOffKey");
|
||||
const String applicationIDString ("com.apple.universalaccess");
|
||||
|
||||
CFUniquePtr<CFStringRef> cfKey (voiceOverKeyString.toCFString());
|
||||
CFUniquePtr<CFStringRef> cfID (applicationIDString.toCFString());
|
||||
|
||||
CFUniquePtr<CFPropertyListRef> value (CFPreferencesCopyAppValue (cfKey.get(), cfID.get()));
|
||||
|
||||
if (value != nullptr)
|
||||
return CFBooleanGetValue ((CFBooleanRef) value.get());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sendAccessibilityEvent (id accessibilityElement,
|
||||
NSAccessibilityNotificationName notification,
|
||||
NSDictionary* userInfo)
|
||||
{
|
||||
jassert (notification != NSAccessibilityNotificationName{});
|
||||
|
||||
NSAccessibilityPostNotificationWithUserInfo (accessibilityElement, notification, userInfo);
|
||||
}
|
||||
|
||||
static void sendHandlerNotification (const AccessibilityHandler& handler,
|
||||
NSAccessibilityNotificationName notification)
|
||||
{
|
||||
if (! areAnyAccessibilityClientsActive() || notification == NSAccessibilityNotificationName{})
|
||||
return;
|
||||
|
||||
if (@available (macOS 10.9, *))
|
||||
{
|
||||
if (id accessibilityElement = static_cast<id> (handler.getNativeImplementation()))
|
||||
{
|
||||
sendAccessibilityEvent (accessibilityElement, notification,
|
||||
(notification == NSAccessibilityLayoutChangedNotification
|
||||
? @{ NSAccessibilityUIElementsKey: @[ accessibilityElement ] }
|
||||
: nil));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static NSAccessibilityNotificationName layoutChangedNotification()
|
||||
{
|
||||
if (@available (macOS 10.9, *))
|
||||
return NSAccessibilityLayoutChangedNotification;
|
||||
|
||||
static NSString* layoutChangedString = @"AXLayoutChanged";
|
||||
return layoutChangedString;
|
||||
}
|
||||
|
||||
void detail::AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler& handler, Event eventType)
|
||||
{
|
||||
auto notification = [eventType]
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case Event::elementCreated: return NSAccessibilityCreatedNotification;
|
||||
case Event::elementDestroyed: return NSAccessibilityUIElementDestroyedNotification;
|
||||
case Event::elementMovedOrResized: return layoutChangedNotification();
|
||||
case Event::focusChanged: return NSAccessibilityFocusedUIElementChangedNotification;
|
||||
case Event::windowOpened: return NSAccessibilityWindowCreatedNotification;
|
||||
case Event::windowClosed: break;
|
||||
}
|
||||
|
||||
return NSAccessibilityNotificationName{};
|
||||
}();
|
||||
|
||||
sendHandlerNotification (handler, notification);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
|
||||
{
|
||||
auto notification = [eventType]
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case AccessibilityEvent::textSelectionChanged: return NSAccessibilitySelectedTextChangedNotification;
|
||||
case AccessibilityEvent::rowSelectionChanged: return NSAccessibilitySelectedRowsChangedNotification;
|
||||
|
||||
case AccessibilityEvent::textChanged:
|
||||
case AccessibilityEvent::valueChanged: return NSAccessibilityValueChangedNotification;
|
||||
case AccessibilityEvent::titleChanged: return NSAccessibilityTitleChangedNotification;
|
||||
case AccessibilityEvent::structureChanged: return layoutChangedNotification();
|
||||
}
|
||||
|
||||
return NSAccessibilityNotificationName{};
|
||||
}();
|
||||
|
||||
sendHandlerNotification (*this, notification);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority priority)
|
||||
{
|
||||
if (! areAnyAccessibilityClientsActive())
|
||||
return;
|
||||
|
||||
if (@available (macOS 10.9, *))
|
||||
{
|
||||
auto nsPriority = [priority]
|
||||
{
|
||||
// The below doesn't get noticed by the @available check above
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability")
|
||||
|
||||
switch (priority)
|
||||
{
|
||||
case AnnouncementPriority::low: return NSAccessibilityPriorityLow;
|
||||
case AnnouncementPriority::medium: return NSAccessibilityPriorityMedium;
|
||||
case AnnouncementPriority::high: return NSAccessibilityPriorityHigh;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return NSAccessibilityPriorityLow;
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}();
|
||||
|
||||
sendAccessibilityEvent (static_cast<id> ([NSApp mainWindow]),
|
||||
NSAccessibilityAnnouncementRequestedNotification,
|
||||
@{ NSAccessibilityAnnouncementKey: juceStringToNS (announcementString),
|
||||
NSAccessibilityPriorityKey: @(nsPriority) });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+331
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
static bool isStartingUpOrShuttingDown()
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
if (app->isInitialising())
|
||||
return true;
|
||||
|
||||
if (auto* mm = MessageManager::getInstanceWithoutCreating())
|
||||
if (mm->hasStopMessageBeenSent())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isHandlerValid (const AccessibilityHandler& handler)
|
||||
{
|
||||
if (auto* provider = handler.getNativeImplementation())
|
||||
return provider->isElementValid();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class AccessibilityHandler::AccessibilityNativeImpl
|
||||
{
|
||||
public:
|
||||
explicit AccessibilityNativeImpl (AccessibilityHandler& owner)
|
||||
: accessibilityElement (new AccessibilityNativeHandle (owner))
|
||||
{
|
||||
++providerCount;
|
||||
}
|
||||
|
||||
~AccessibilityNativeImpl()
|
||||
{
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
accessibilityElement->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
|
||||
|
||||
accessibilityElement->invalidateElement();
|
||||
--providerCount;
|
||||
|
||||
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
{
|
||||
uiaWrapper->disconnectProvider (provider);
|
||||
|
||||
if (providerCount == 0 && JUCEApplicationBase::isStandaloneApp())
|
||||
uiaWrapper->disconnectAllProviders();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ComSmartPtr<AccessibilityNativeHandle> accessibilityElement;
|
||||
static int providerCount;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeImpl)
|
||||
};
|
||||
|
||||
int AccessibilityHandler::AccessibilityNativeImpl::providerCount = 0;
|
||||
|
||||
//==============================================================================
|
||||
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
|
||||
{
|
||||
return nativeImpl->accessibilityElement;
|
||||
}
|
||||
|
||||
static bool areAnyAccessibilityClientsActive()
|
||||
{
|
||||
const auto areClientsListening = []
|
||||
{
|
||||
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
return uiaWrapper->clientsAreListening() != 0;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const auto isScreenReaderRunning = []
|
||||
{
|
||||
BOOL isRunning = FALSE;
|
||||
SystemParametersInfo (SPI_GETSCREENREADER, 0, (PVOID) &isRunning, 0);
|
||||
|
||||
return isRunning != 0;
|
||||
};
|
||||
|
||||
return areClientsListening() || isScreenReaderRunning();
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void getProviderWithCheckedWrapper (const AccessibilityHandler& handler, Callback&& callback)
|
||||
{
|
||||
if (! areAnyAccessibilityClientsActive() || isStartingUpOrShuttingDown() || ! isHandlerValid (handler))
|
||||
return;
|
||||
|
||||
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
{
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
handler.getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
|
||||
|
||||
callback (uiaWrapper, provider);
|
||||
}
|
||||
}
|
||||
|
||||
void sendAccessibilityAutomationEvent (const AccessibilityHandler& handler, EVENTID event)
|
||||
{
|
||||
jassert (event != EVENTID{});
|
||||
|
||||
getProviderWithCheckedWrapper (handler, [event] (WindowsUIAWrapper* uiaWrapper, ComSmartPtr<IRawElementProviderSimple>& provider)
|
||||
{
|
||||
uiaWrapper->raiseAutomationEvent (provider, event);
|
||||
});
|
||||
}
|
||||
|
||||
void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler& handler, PROPERTYID property, VARIANT newValue)
|
||||
{
|
||||
jassert (property != PROPERTYID{});
|
||||
|
||||
getProviderWithCheckedWrapper (handler, [property, newValue] (WindowsUIAWrapper* uiaWrapper, ComSmartPtr<IRawElementProviderSimple>& provider)
|
||||
{
|
||||
VARIANT oldValue;
|
||||
VariantHelpers::clear (&oldValue);
|
||||
|
||||
uiaWrapper->raiseAutomationPropertyChangedEvent (provider, property, oldValue, newValue);
|
||||
});
|
||||
}
|
||||
|
||||
void detail::AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler& handler, Event eventType)
|
||||
{
|
||||
using namespace ComTypes::Constants;
|
||||
|
||||
if (eventType == Event::elementCreated
|
||||
|| eventType == Event::elementDestroyed)
|
||||
{
|
||||
if (auto* parent = handler.getParent())
|
||||
sendAccessibilityAutomationEvent (*parent, UIA_LayoutInvalidatedEventId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventType == Event::windowOpened
|
||||
|| eventType == Event::windowClosed)
|
||||
{
|
||||
if (auto* peer = handler.getComponent().getPeer())
|
||||
if ((peer->getStyleFlags() & ComponentPeer::windowHasTitleBar) == 0)
|
||||
return;
|
||||
}
|
||||
|
||||
auto event = [eventType]() -> EVENTID
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case Event::focusChanged: return UIA_AutomationFocusChangedEventId;
|
||||
case Event::windowOpened: return UIA_Window_WindowOpenedEventId;
|
||||
case Event::windowClosed: return UIA_Window_WindowClosedEventId;
|
||||
case Event::elementCreated:
|
||||
case Event::elementDestroyed:
|
||||
case Event::elementMovedOrResized: break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
if (event != EVENTID{})
|
||||
sendAccessibilityAutomationEvent (handler, event);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
|
||||
{
|
||||
if (eventType == AccessibilityEvent::titleChanged)
|
||||
{
|
||||
VARIANT newValue;
|
||||
VariantHelpers::setString (getTitle(), &newValue);
|
||||
|
||||
sendAccessibilityPropertyChangedEvent (*this, UIA_NamePropertyId, newValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventType == AccessibilityEvent::valueChanged)
|
||||
{
|
||||
if (auto* valueInterface = getValueInterface())
|
||||
{
|
||||
const auto propertyType = getRole() == AccessibilityRole::slider ? UIA_RangeValueValuePropertyId
|
||||
: UIA_ValueValuePropertyId;
|
||||
|
||||
const auto value = getRole() == AccessibilityRole::slider
|
||||
? VariantHelpers::getWithValue (valueInterface->getCurrentValue())
|
||||
: VariantHelpers::getWithValue (valueInterface->getCurrentValueAsString());
|
||||
|
||||
sendAccessibilityPropertyChangedEvent (*this, propertyType, value);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto event = [eventType]() -> EVENTID
|
||||
{
|
||||
using namespace ComTypes::Constants;
|
||||
|
||||
switch (eventType)
|
||||
{
|
||||
case AccessibilityEvent::textSelectionChanged: return UIA_Text_TextSelectionChangedEventId;
|
||||
case AccessibilityEvent::textChanged: return UIA_Text_TextChangedEventId;
|
||||
case AccessibilityEvent::structureChanged: return UIA_StructureChangedEventId;
|
||||
case AccessibilityEvent::rowSelectionChanged: return UIA_SelectionItem_ElementSelectedEventId;
|
||||
case AccessibilityEvent::titleChanged:
|
||||
case AccessibilityEvent::valueChanged: break;
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
if (event != EVENTID{})
|
||||
sendAccessibilityAutomationEvent (*this, event);
|
||||
}
|
||||
|
||||
struct SpVoiceWrapper final : public DeletedAtShutdown
|
||||
{
|
||||
SpVoiceWrapper()
|
||||
{
|
||||
[[maybe_unused]] auto hr = voice.CoCreateInstance (ComTypes::CLSID_SpVoice);
|
||||
|
||||
jassert (SUCCEEDED (hr));
|
||||
}
|
||||
|
||||
~SpVoiceWrapper() override
|
||||
{
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
ComSmartPtr<ISpVoice> voice;
|
||||
|
||||
JUCE_DECLARE_SINGLETON (SpVoiceWrapper, false)
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (SpVoiceWrapper)
|
||||
|
||||
|
||||
void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority priority)
|
||||
{
|
||||
if (! areAnyAccessibilityClientsActive())
|
||||
return;
|
||||
|
||||
if (auto* sharedVoice = SpVoiceWrapper::getInstance())
|
||||
{
|
||||
auto voicePriority = [priority]
|
||||
{
|
||||
switch (priority)
|
||||
{
|
||||
case AnnouncementPriority::low: return SPVPRI_OVER;
|
||||
case AnnouncementPriority::medium: return SPVPRI_NORMAL;
|
||||
case AnnouncementPriority::high: return SPVPRI_ALERT;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return SPVPRI_OVER;
|
||||
}();
|
||||
|
||||
sharedVoice->voice->SetPriority (voicePriority);
|
||||
sharedVoice->voice->Speak (announcementString.toWideCharPointer(), SPF_ASYNC, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
namespace WindowsAccessibility
|
||||
{
|
||||
static long getUiaRootObjectId()
|
||||
{
|
||||
return static_cast<long> (UiaRootObjectId);
|
||||
}
|
||||
|
||||
static bool handleWmGetObject (AccessibilityHandler* handler, WPARAM wParam, LPARAM lParam, LRESULT* res)
|
||||
{
|
||||
if (isStartingUpOrShuttingDown() || (handler == nullptr || ! isHandlerValid (*handler)))
|
||||
return false;
|
||||
|
||||
if (auto* uiaWrapper = WindowsUIAWrapper::getInstance())
|
||||
{
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
|
||||
|
||||
if (! uiaWrapper->isProviderDisconnecting (provider))
|
||||
*res = uiaWrapper->returnRawElementProvider ((HWND) handler->getComponent().getWindowHandle(), wParam, lParam, provider);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void revokeUIAMapEntriesForWindow (HWND hwnd)
|
||||
{
|
||||
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
uiaWrapper->returnRawElementProvider (hwnd, 0, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (WindowsUIAWrapper)
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
} // namespace juce
|
||||
+500
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::ComTypes
|
||||
{
|
||||
|
||||
/*
|
||||
These interfaces would normally be included in the system platform headers.
|
||||
However, those headers are likely to be incomplete when building with
|
||||
MinGW. In order to allow building accessible applications under MinGW,
|
||||
we reproduce all necessary definitions here.
|
||||
*/
|
||||
|
||||
struct UiaPoint
|
||||
{
|
||||
double x;
|
||||
double y;
|
||||
};
|
||||
|
||||
struct UiaRect
|
||||
{
|
||||
double left;
|
||||
double top;
|
||||
double width;
|
||||
double height;
|
||||
};
|
||||
|
||||
enum NavigateDirection
|
||||
{
|
||||
NavigateDirection_Parent = 0,
|
||||
NavigateDirection_NextSibling = 1,
|
||||
NavigateDirection_PreviousSibling = 2,
|
||||
NavigateDirection_FirstChild = 3,
|
||||
NavigateDirection_LastChild = 4
|
||||
};
|
||||
|
||||
enum ExpandCollapseState
|
||||
{
|
||||
ExpandCollapseState_Collapsed = 0,
|
||||
ExpandCollapseState_Expanded = 1,
|
||||
ExpandCollapseState_PartiallyExpanded = 2,
|
||||
ExpandCollapseState_LeafNode = 3
|
||||
};
|
||||
|
||||
enum TextPatternRangeEndpoint
|
||||
{
|
||||
TextPatternRangeEndpoint_Start = 0,
|
||||
TextPatternRangeEndpoint_End = 1
|
||||
};
|
||||
|
||||
enum TextUnit
|
||||
{
|
||||
TextUnit_Character = 0,
|
||||
TextUnit_Format = 1,
|
||||
TextUnit_Word = 2,
|
||||
TextUnit_Line = 3,
|
||||
TextUnit_Paragraph = 4,
|
||||
TextUnit_Page = 5,
|
||||
TextUnit_Document = 6
|
||||
};
|
||||
|
||||
enum SupportedTextSelection
|
||||
{
|
||||
SupportedTextSelection_None = 0,
|
||||
SupportedTextSelection_Single = 1,
|
||||
SupportedTextSelection_Multiple = 2
|
||||
};
|
||||
|
||||
enum CaretPosition
|
||||
{
|
||||
CaretPosition_Unknown = 0,
|
||||
CaretPosition_EndOfLine = 1,
|
||||
CaretPosition_BeginningOfLine = 2
|
||||
};
|
||||
|
||||
enum ToggleState
|
||||
{
|
||||
ToggleState_Off = 0,
|
||||
ToggleState_On = 1,
|
||||
ToggleState_Indeterminate = 2
|
||||
};
|
||||
|
||||
enum WindowVisualState
|
||||
{
|
||||
WindowVisualState_Normal = 0,
|
||||
WindowVisualState_Maximized = 1,
|
||||
WindowVisualState_Minimized = 2
|
||||
};
|
||||
|
||||
enum WindowInteractionState
|
||||
{
|
||||
WindowInteractionState_Running = 0,
|
||||
WindowInteractionState_Closing = 1,
|
||||
WindowInteractionState_ReadyForUserInteraction = 2,
|
||||
WindowInteractionState_BlockedByModalWindow = 3,
|
||||
WindowInteractionState_NotResponding = 4
|
||||
};
|
||||
|
||||
enum RowOrColumnMajor
|
||||
{
|
||||
RowOrColumnMajor_RowMajor = 0,
|
||||
RowOrColumnMajor_ColumnMajor = 1,
|
||||
RowOrColumnMajor_Indeterminate = 2
|
||||
};
|
||||
|
||||
enum ScrollAmount
|
||||
{
|
||||
ScrollAmount_LargeDecrement = 0,
|
||||
ScrollAmount_SmallDecrement = 1,
|
||||
ScrollAmount_NoAmount = 2,
|
||||
ScrollAmount_LargeIncrement = 3,
|
||||
ScrollAmount_SmallIncrement = 4
|
||||
};
|
||||
|
||||
namespace Constants
|
||||
{
|
||||
|
||||
#undef UIA_InvokePatternId
|
||||
#undef UIA_SelectionPatternId
|
||||
#undef UIA_ValuePatternId
|
||||
#undef UIA_RangeValuePatternId
|
||||
#undef UIA_ScrollPatternId
|
||||
#undef UIA_ExpandCollapsePatternId
|
||||
#undef UIA_GridPatternId
|
||||
#undef UIA_GridItemPatternId
|
||||
#undef UIA_WindowPatternId
|
||||
#undef UIA_SelectionItemPatternId
|
||||
#undef UIA_TablePatternId
|
||||
#undef UIA_TableItemPatternId
|
||||
#undef UIA_TextPatternId
|
||||
#undef UIA_TogglePatternId
|
||||
#undef UIA_TransformPatternId
|
||||
#undef UIA_ScrollItemPatternId
|
||||
#undef UIA_TextPattern2Id
|
||||
#undef UIA_StructureChangedEventId
|
||||
#undef UIA_MenuOpenedEventId
|
||||
#undef UIA_AutomationFocusChangedEventId
|
||||
#undef UIA_MenuClosedEventId
|
||||
#undef UIA_LayoutInvalidatedEventId
|
||||
#undef UIA_Invoke_InvokedEventId
|
||||
#undef UIA_SelectionItem_ElementSelectedEventId
|
||||
#undef UIA_Text_TextSelectionChangedEventId
|
||||
#undef UIA_Text_TextChangedEventId
|
||||
#undef UIA_Window_WindowOpenedEventId
|
||||
#undef UIA_Window_WindowClosedEventId
|
||||
#undef UIA_IsPeripheralPropertyId
|
||||
#undef UIA_FullDescriptionPropertyId
|
||||
#undef UIA_IsDialogPropertyId
|
||||
#undef UIA_IsReadOnlyAttributeId
|
||||
#undef UIA_CaretPositionAttributeId
|
||||
#undef UIA_ButtonControlTypeId
|
||||
#undef UIA_CheckBoxControlTypeId
|
||||
#undef UIA_ComboBoxControlTypeId
|
||||
#undef UIA_EditControlTypeId
|
||||
#undef UIA_HyperlinkControlTypeId
|
||||
#undef UIA_ImageControlTypeId
|
||||
#undef UIA_ListItemControlTypeId
|
||||
#undef UIA_ListControlTypeId
|
||||
#undef UIA_MenuBarControlTypeId
|
||||
#undef UIA_MenuItemControlTypeId
|
||||
#undef UIA_ProgressBarControlTypeId
|
||||
#undef UIA_RadioButtonControlTypeId
|
||||
#undef UIA_ScrollBarControlTypeId
|
||||
#undef UIA_SliderControlTypeId
|
||||
#undef UIA_TextControlTypeId
|
||||
#undef UIA_ToolTipControlTypeId
|
||||
#undef UIA_TreeControlTypeId
|
||||
#undef UIA_TreeItemControlTypeId
|
||||
#undef UIA_CustomControlTypeId
|
||||
#undef UIA_GroupControlTypeId
|
||||
#undef UIA_DataItemControlTypeId
|
||||
#undef UIA_WindowControlTypeId
|
||||
#undef UIA_HeaderControlTypeId
|
||||
#undef UIA_HeaderItemControlTypeId
|
||||
#undef UIA_TableControlTypeId
|
||||
|
||||
const long UIA_InvokePatternId = 10000;
|
||||
const long UIA_SelectionPatternId = 10001;
|
||||
const long UIA_ValuePatternId = 10002;
|
||||
const long UIA_RangeValuePatternId = 10003;
|
||||
const long UIA_ScrollPatternId = 10004;
|
||||
const long UIA_ExpandCollapsePatternId = 10005;
|
||||
const long UIA_GridPatternId = 10006;
|
||||
const long UIA_GridItemPatternId = 10007;
|
||||
const long UIA_WindowPatternId = 10009;
|
||||
const long UIA_SelectionItemPatternId = 10010;
|
||||
const long UIA_TablePatternId = 10012;
|
||||
const long UIA_TableItemPatternId = 10013;
|
||||
const long UIA_TextPatternId = 10014;
|
||||
const long UIA_TogglePatternId = 10015;
|
||||
const long UIA_TransformPatternId = 10016;
|
||||
const long UIA_ScrollItemPatternId = 10017;
|
||||
const long UIA_TextPattern2Id = 10024;
|
||||
const long UIA_StructureChangedEventId = 20002;
|
||||
const long UIA_MenuOpenedEventId = 20003;
|
||||
const long UIA_AutomationFocusChangedEventId = 20005;
|
||||
const long UIA_MenuClosedEventId = 20007;
|
||||
const long UIA_LayoutInvalidatedEventId = 20008;
|
||||
const long UIA_Invoke_InvokedEventId = 20009;
|
||||
const long UIA_SelectionItem_ElementSelectedEventId = 20012;
|
||||
const long UIA_Text_TextSelectionChangedEventId = 20014;
|
||||
const long UIA_Text_TextChangedEventId = 20015;
|
||||
const long UIA_Window_WindowOpenedEventId = 20016;
|
||||
const long UIA_Window_WindowClosedEventId = 20017;
|
||||
const long UIA_IsPeripheralPropertyId = 30150;
|
||||
const long UIA_FullDescriptionPropertyId = 30159;
|
||||
const long UIA_IsDialogPropertyId = 30174;
|
||||
const long UIA_IsReadOnlyAttributeId = 40015;
|
||||
const long UIA_CaretPositionAttributeId = 40038;
|
||||
const long UIA_ButtonControlTypeId = 50000;
|
||||
const long UIA_CheckBoxControlTypeId = 50002;
|
||||
const long UIA_ComboBoxControlTypeId = 50003;
|
||||
const long UIA_EditControlTypeId = 50004;
|
||||
const long UIA_HyperlinkControlTypeId = 50005;
|
||||
const long UIA_ImageControlTypeId = 50006;
|
||||
const long UIA_ListItemControlTypeId = 50007;
|
||||
const long UIA_ListControlTypeId = 50008;
|
||||
const long UIA_MenuBarControlTypeId = 50010;
|
||||
const long UIA_MenuItemControlTypeId = 50011;
|
||||
const long UIA_ProgressBarControlTypeId = 50012;
|
||||
const long UIA_RadioButtonControlTypeId = 50013;
|
||||
const long UIA_ScrollBarControlTypeId = 50014;
|
||||
const long UIA_SliderControlTypeId = 50015;
|
||||
const long UIA_TextControlTypeId = 50020;
|
||||
const long UIA_ToolTipControlTypeId = 50022;
|
||||
const long UIA_TreeControlTypeId = 50023;
|
||||
const long UIA_TreeItemControlTypeId = 50024;
|
||||
const long UIA_CustomControlTypeId = 50025;
|
||||
const long UIA_GroupControlTypeId = 50026;
|
||||
const long UIA_DataItemControlTypeId = 50029;
|
||||
const long UIA_WindowControlTypeId = 50032;
|
||||
const long UIA_HeaderControlTypeId = 50034;
|
||||
const long UIA_HeaderItemControlTypeId = 50035;
|
||||
const long UIA_TableControlTypeId = 50036;
|
||||
|
||||
} // namespace Constants
|
||||
|
||||
interface IRawElementProviderFragmentRoot;
|
||||
interface IRawElementProviderFragment;
|
||||
|
||||
JUCE_COMCLASS (IRawElementProviderFragmentRoot, "620ce2a5-ab8f-40a9-86cb-de3c75599b58") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL ElementProviderFromPoint (double x, double y, __RPC__deref_out_opt IRawElementProviderFragment** pRetVal) = 0;
|
||||
JUCE_COMCALL GetFocus (__RPC__deref_out_opt IRawElementProviderFragment * *pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IRawElementProviderFragment, "f7063da8-8359-439c-9297-bbc5299a7d87") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL Navigate (NavigateDirection direction, __RPC__deref_out_opt IRawElementProviderFragment** pRetVal) = 0;
|
||||
JUCE_COMCALL GetRuntimeId (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
|
||||
JUCE_COMCALL get_BoundingRectangle (__RPC__out UiaRect * pRetVal) = 0;
|
||||
JUCE_COMCALL GetEmbeddedFragmentRoots (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
|
||||
JUCE_COMCALL SetFocus() = 0;
|
||||
JUCE_COMCALL get_FragmentRoot (__RPC__deref_out_opt IRawElementProviderFragmentRoot * *pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IRawElementProviderHwndOverride, "1d5df27c-8947-4425-b8d9-79787bb460b8") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL GetOverrideProviderForHwnd (__RPC__in HWND hwnd,
|
||||
__RPC__deref_out_opt IRawElementProviderSimple** pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IExpandCollapseProvider, "d847d3a5-cab0-4a98-8c32-ecb45c59ad24") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL Expand() = 0;
|
||||
JUCE_COMCALL Collapse() = 0;
|
||||
JUCE_COMCALL get_ExpandCollapseState (__RPC__out ExpandCollapseState * pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IGridItemProvider, "d02541f1-fb81-4d64-ae32-f520f8a6dbd1") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL get_Row (__RPC__out int* pRetVal) = 0;
|
||||
JUCE_COMCALL get_Column (__RPC__out int* pRetVal) = 0;
|
||||
JUCE_COMCALL get_RowSpan (__RPC__out int* pRetVal) = 0;
|
||||
JUCE_COMCALL get_ColumnSpan (__RPC__out int* pRetVal) = 0;
|
||||
JUCE_COMCALL get_ContainingGrid (__RPC__deref_out_opt IRawElementProviderSimple * *pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IGridProvider, "b17d6187-0907-464b-a168-0ef17a1572b1") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL GetItem (int row, int column, __RPC__deref_out_opt IRawElementProviderSimple** pRetVal) = 0;
|
||||
JUCE_COMCALL get_RowCount (__RPC__out int* pRetVal) = 0;
|
||||
JUCE_COMCALL get_ColumnCount (__RPC__out int* pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (ITableItemProvider, "b9734fa6-771f-4d78-9c90-2517999349cd") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL GetRowHeaderItems (SAFEARRAY** pRetVal) = 0;
|
||||
JUCE_COMCALL GetColumnHeaderItems (SAFEARRAY** pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (ITableProvider, "9c860395-97b3-490a-b52a-858cc22af166") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL GetRowHeaders (SAFEARRAY** pRetVal) = 0;
|
||||
JUCE_COMCALL GetColumnHeaders (SAFEARRAY** pRetVal) = 0;
|
||||
JUCE_COMCALL get_RowOrColumnMajor (RowOrColumnMajor* pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IInvokeProvider, "54fcb24b-e18e-47a2-b4d3-eccbe77599a2") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL Invoke() = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IRangeValueProvider, "36dc7aef-33e6-4691-afe1-2be7274b3d33") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL SetValue (double val) = 0;
|
||||
JUCE_COMCALL get_Value (__RPC__out double* pRetVal) = 0;
|
||||
JUCE_COMCALL get_IsReadOnly (__RPC__out BOOL * pRetVal) = 0;
|
||||
JUCE_COMCALL get_Maximum (__RPC__out double* pRetVal) = 0;
|
||||
JUCE_COMCALL get_Minimum (__RPC__out double* pRetVal) = 0;
|
||||
JUCE_COMCALL get_LargeChange (__RPC__out double* pRetVal) = 0;
|
||||
JUCE_COMCALL get_SmallChange (__RPC__out double* pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (ISelectionProvider, "fb8b03af-3bdf-48d4-bd36-1a65793be168") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL GetSelection (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
|
||||
JUCE_COMCALL get_CanSelectMultiple (__RPC__out BOOL * pRetVal) = 0;
|
||||
JUCE_COMCALL get_IsSelectionRequired (__RPC__out BOOL * pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (ISelectionProvider2, "14f68475-ee1c-44f6-a869-d239381f0fe7") : public ISelectionProvider
|
||||
{
|
||||
JUCE_COMCALL get_FirstSelectedItem (IRawElementProviderSimple * *retVal) = 0;
|
||||
JUCE_COMCALL get_LastSelectedItem (IRawElementProviderSimple * *retVal) = 0;
|
||||
JUCE_COMCALL get_CurrentSelectedItem (IRawElementProviderSimple * *retVal) = 0;
|
||||
JUCE_COMCALL get_ItemCount (int* retVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (ISelectionItemProvider, "2acad808-b2d4-452d-a407-91ff1ad167b2") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL Select() = 0;
|
||||
JUCE_COMCALL AddToSelection() = 0;
|
||||
JUCE_COMCALL RemoveFromSelection() = 0;
|
||||
JUCE_COMCALL get_IsSelected (__RPC__out BOOL * pRetVal) = 0;
|
||||
JUCE_COMCALL get_SelectionContainer (__RPC__deref_out_opt IRawElementProviderSimple * *pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (ITextRangeProvider, "5347ad7b-c355-46f8-aff5-909033582f63") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL Clone (__RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
|
||||
JUCE_COMCALL Compare (__RPC__in_opt ITextRangeProvider * range, __RPC__out BOOL * pRetVal) = 0;
|
||||
JUCE_COMCALL CompareEndpoints (TextPatternRangeEndpoint endpoint, __RPC__in_opt ITextRangeProvider * targetRange, TextPatternRangeEndpoint targetEndpoint, __RPC__out int* pRetVal) = 0;
|
||||
JUCE_COMCALL ExpandToEnclosingUnit (TextUnit unit) = 0;
|
||||
JUCE_COMCALL FindAttribute (TEXTATTRIBUTEID attributeId, VARIANT val, BOOL backward, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
|
||||
JUCE_COMCALL FindText (__RPC__in BSTR text, BOOL backward, BOOL ignoreCase, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
|
||||
JUCE_COMCALL GetAttributeValue (TEXTATTRIBUTEID attributeId, __RPC__out VARIANT * pRetVal) = 0;
|
||||
JUCE_COMCALL GetBoundingRectangles (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
|
||||
JUCE_COMCALL GetEnclosingElement (__RPC__deref_out_opt IRawElementProviderSimple * *pRetVal) = 0;
|
||||
JUCE_COMCALL GetText (int maxLength, __RPC__deref_out_opt BSTR* pRetVal) = 0;
|
||||
JUCE_COMCALL Move (TextUnit unit, int count, __RPC__out int* pRetVal) = 0;
|
||||
JUCE_COMCALL MoveEndpointByUnit (TextPatternRangeEndpoint endpoint, TextUnit unit, int count, __RPC__out int* pRetVal) = 0;
|
||||
JUCE_COMCALL MoveEndpointByRange (TextPatternRangeEndpoint endpoint, __RPC__in_opt ITextRangeProvider * targetRange, TextPatternRangeEndpoint targetEndpoint) = 0;
|
||||
JUCE_COMCALL Select() = 0;
|
||||
JUCE_COMCALL AddToSelection() = 0;
|
||||
JUCE_COMCALL RemoveFromSelection() = 0;
|
||||
JUCE_COMCALL ScrollIntoView (BOOL alignToTop) = 0;
|
||||
JUCE_COMCALL GetChildren (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (ITextProvider, "3589c92c-63f3-4367-99bb-ada653b77cf2") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL GetSelection (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
|
||||
JUCE_COMCALL GetVisibleRanges (__RPC__deref_out_opt SAFEARRAY * *pRetVal) = 0;
|
||||
JUCE_COMCALL RangeFromChild (__RPC__in_opt IRawElementProviderSimple * childElement, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
|
||||
JUCE_COMCALL RangeFromPoint (UiaPoint point, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
|
||||
JUCE_COMCALL get_DocumentRange (__RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
|
||||
JUCE_COMCALL get_SupportedTextSelection (__RPC__out SupportedTextSelection * pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (ITextProvider2, "0dc5e6ed-3e16-4bf1-8f9a-a979878bc195") : public ITextProvider
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL RangeFromAnnotation (__RPC__in_opt IRawElementProviderSimple * annotationElement, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
|
||||
JUCE_COMCALL GetCaretRange (__RPC__out BOOL * isActive, __RPC__deref_out_opt ITextRangeProvider * *pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IToggleProvider, "56d00bd0-c4f4-433c-a836-1a52a57e0892") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL Toggle() = 0;
|
||||
JUCE_COMCALL get_ToggleState (__RPC__out ToggleState * pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (ITransformProvider, "6829ddc4-4f91-4ffa-b86f-bd3e2987cb4c") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL Move (double x, double y) = 0;
|
||||
JUCE_COMCALL Resize (double width, double height) = 0;
|
||||
JUCE_COMCALL Rotate (double degrees) = 0;
|
||||
JUCE_COMCALL get_CanMove (__RPC__out BOOL * pRetVal) = 0;
|
||||
JUCE_COMCALL get_CanResize (__RPC__out BOOL * pRetVal) = 0;
|
||||
JUCE_COMCALL get_CanRotate (__RPC__out BOOL * pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IValueProvider, "c7935180-6fb3-4201-b174-7df73adbf64a") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL SetValue (__RPC__in LPCWSTR val) = 0;
|
||||
JUCE_COMCALL get_Value (__RPC__deref_out_opt BSTR * pRetVal) = 0;
|
||||
JUCE_COMCALL get_IsReadOnly (__RPC__out BOOL * pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IWindowProvider, "987df77b-db06-4d77-8f8a-86a9c3bb90b9") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL SetVisualState (WindowVisualState state) = 0;
|
||||
JUCE_COMCALL Close() = 0;
|
||||
JUCE_COMCALL WaitForInputIdle (int milliseconds, __RPC__out BOOL* pRetVal) = 0;
|
||||
JUCE_COMCALL get_CanMaximize (__RPC__out BOOL * pRetVal) = 0;
|
||||
JUCE_COMCALL get_CanMinimize (__RPC__out BOOL * pRetVal) = 0;
|
||||
JUCE_COMCALL get_IsModal (__RPC__out BOOL * pRetVal) = 0;
|
||||
JUCE_COMCALL get_WindowVisualState (__RPC__out WindowVisualState * pRetVal) = 0;
|
||||
JUCE_COMCALL get_WindowInteractionState (__RPC__out WindowInteractionState * pRetVal) = 0;
|
||||
JUCE_COMCALL get_IsTopmost (__RPC__out BOOL * pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IScrollProvider, "b38b8077-1fc3-42a5-8cae-d40c2215055a") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL Scroll (ScrollAmount horizontalAmount, ScrollAmount verticalAmount) = 0;
|
||||
JUCE_COMCALL SetScrollPercent (double horizontalPercent,double verticalPercent) = 0;
|
||||
JUCE_COMCALL get_HorizontalScrollPercent (double* pRetVal) = 0;
|
||||
JUCE_COMCALL get_VerticalScrollPercent (double* pRetVal) = 0;
|
||||
JUCE_COMCALL get_HorizontalViewSize (double* pRetVal) = 0;
|
||||
JUCE_COMCALL get_VerticalViewSize (double* pRetVal) = 0;
|
||||
JUCE_COMCALL get_HorizontallyScrollable (BOOL* pRetVal) = 0;
|
||||
JUCE_COMCALL get_VerticallyScrollable (BOOL* pRetVal) = 0;
|
||||
};
|
||||
|
||||
JUCE_COMCLASS (IScrollItemProvider, "2360c714-4bf1-4b26-ba65-9b21316127eb") : public IUnknown
|
||||
{
|
||||
public:
|
||||
JUCE_COMCALL ScrollIntoView() = 0;
|
||||
};
|
||||
|
||||
constexpr CLSID CLSID_SpVoice { 0x96749377, 0x3391, 0x11D2, { 0x9E, 0xE3, 0x00, 0xC0, 0x4F, 0x79, 0x73, 0x96 } };
|
||||
|
||||
} // namespace juce::ComTypes
|
||||
|
||||
#ifdef __CRT_UUID_DECL
|
||||
__CRT_UUID_DECL (juce::ComTypes::IRawElementProviderFragmentRoot, 0x620ce2a5, 0xab8f, 0x40a9, 0x86, 0xcb, 0xde, 0x3c, 0x75, 0x59, 0x9b, 0x58)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IRawElementProviderFragment, 0xf7063da8, 0x8359, 0x439c, 0x92, 0x97, 0xbb, 0xc5, 0x29, 0x9a, 0x7d, 0x87)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IRawElementProviderHwndOverride, 0x1d5df27c, 0x8947, 0x4425, 0xb8, 0xd9, 0x79, 0x78, 0x7b, 0xb4, 0x60, 0xb8)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IExpandCollapseProvider, 0xd847d3a5, 0xcab0, 0x4a98, 0x8c, 0x32, 0xec, 0xb4, 0x5c, 0x59, 0xad, 0x24)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IGridItemProvider, 0xd02541f1, 0xfb81, 0x4d64, 0xae, 0x32, 0xf5, 0x20, 0xf8, 0xa6, 0xdb, 0xd1)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IGridProvider, 0xb17d6187, 0x0907, 0x464b, 0xa1, 0x68, 0x0e, 0xf1, 0x7a, 0x15, 0x72, 0xb1)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IInvokeProvider, 0x54fcb24b, 0xe18e, 0x47a2, 0xb4, 0xd3, 0xec, 0xcb, 0xe7, 0x75, 0x99, 0xa2)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IRangeValueProvider, 0x36dc7aef, 0x33e6, 0x4691, 0xaf, 0xe1, 0x2b, 0xe7, 0x27, 0x4b, 0x3d, 0x33)
|
||||
__CRT_UUID_DECL (juce::ComTypes::ISelectionProvider, 0xfb8b03af, 0x3bdf, 0x48d4, 0xbd, 0x36, 0x1a, 0x65, 0x79, 0x3b, 0xe1, 0x68)
|
||||
__CRT_UUID_DECL (juce::ComTypes::ISelectionProvider2, 0x14f68475, 0xee1c, 0x44f6, 0xa8, 0x69, 0xd2, 0x39, 0x38, 0x1f, 0x0f, 0xe7)
|
||||
__CRT_UUID_DECL (juce::ComTypes::ISelectionItemProvider, 0x2acad808, 0xb2d4, 0x452d, 0xa4, 0x07, 0x91, 0xff, 0x1a, 0xd1, 0x67, 0xb2)
|
||||
__CRT_UUID_DECL (juce::ComTypes::ITextRangeProvider, 0x5347ad7b, 0xc355, 0x46f8, 0xaf, 0xf5, 0x90, 0x90, 0x33, 0x58, 0x2f, 0x63)
|
||||
__CRT_UUID_DECL (juce::ComTypes::ITextProvider, 0x3589c92c, 0x63f3, 0x4367, 0x99, 0xbb, 0xad, 0xa6, 0x53, 0xb7, 0x7c, 0xf2)
|
||||
__CRT_UUID_DECL (juce::ComTypes::ITextProvider2, 0x0dc5e6ed, 0x3e16, 0x4bf1, 0x8f, 0x9a, 0xa9, 0x79, 0x87, 0x8b, 0xc1, 0x95)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IToggleProvider, 0x56d00bd0, 0xc4f4, 0x433c, 0xa8, 0x36, 0x1a, 0x52, 0xa5, 0x7e, 0x08, 0x92)
|
||||
__CRT_UUID_DECL (juce::ComTypes::ITransformProvider, 0x6829ddc4, 0x4f91, 0x4ffa, 0xb8, 0x6f, 0xbd, 0x3e, 0x29, 0x87, 0xcb, 0x4c)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IValueProvider, 0xc7935180, 0x6fb3, 0x4201, 0xb1, 0x74, 0x7d, 0xf7, 0x3a, 0xdb, 0xf6, 0x4a)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IWindowProvider, 0x987df77b, 0xdb06, 0x4d77, 0x8f, 0x8a, 0x86, 0xa9, 0xc3, 0xbb, 0x90, 0xb9)
|
||||
__CRT_UUID_DECL (juce::ComTypes::ITableItemProvider, 0xb9734fa6, 0x771f, 0x4d78, 0x9c, 0x90, 0x25, 0x17, 0x99, 0x93, 0x49, 0xcd)
|
||||
__CRT_UUID_DECL (juce::ComTypes::ITableProvider, 0x9c860395, 0x97b3, 0x490a, 0xb5, 0x2a, 0x85, 0x8c, 0xc2, 0x2a, 0xf1, 0x66)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IScrollProvider, 0xb38b8077, 0x1fc3, 0x42a5, 0x8c, 0xae, 0xd4, 0x0c, 0x22, 0x15, 0x05, 0x5a)
|
||||
__CRT_UUID_DECL (juce::ComTypes::IScrollItemProvider, 0x2360c714, 0x4bf1, 0x4b26, 0xba, 0x65, 0x9b, 0x21, 0x31, 0x61, 0x27, 0xeb)
|
||||
#endif
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIAExpandCollapseProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::IExpandCollapseProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Expand() override
|
||||
{
|
||||
return invokeShowMenu();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Collapse() override
|
||||
{
|
||||
return invokeShowMenu();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ExpandCollapseState (ComTypes::ExpandCollapseState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = getHandler().getCurrentState().isExpanded()
|
||||
? ComTypes::ExpandCollapseState_Expanded
|
||||
: ComTypes::ExpandCollapseState_Collapsed;
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
JUCE_COMRESULT invokeShowMenu()
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (handler.getActions().invoke (AccessibilityActionType::showMenu))
|
||||
{
|
||||
using namespace ComTypes::Constants;
|
||||
|
||||
sendAccessibilityAutomationEvent (handler, handler.getCurrentState().isExpanded()
|
||||
? UIA_MenuOpenedEventId
|
||||
: UIA_MenuClosedEventId);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAExpandCollapseProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIAGridItemProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::IGridItemProvider, ComTypes::ITableItemProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT get_Row (int* pRetVal) override
|
||||
{
|
||||
return withTableSpan (pRetVal,
|
||||
&AccessibilityTableInterface::getRowSpan,
|
||||
&AccessibilityTableInterface::Span::begin);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Column (int* pRetVal) override
|
||||
{
|
||||
return withTableSpan (pRetVal,
|
||||
&AccessibilityTableInterface::getColumnSpan,
|
||||
&AccessibilityTableInterface::Span::begin);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_RowSpan (int* pRetVal) override
|
||||
{
|
||||
return withTableSpan (pRetVal,
|
||||
&AccessibilityTableInterface::getRowSpan,
|
||||
&AccessibilityTableInterface::Span::num);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ColumnSpan (int* pRetVal) override
|
||||
{
|
||||
return withTableSpan (pRetVal,
|
||||
&AccessibilityTableInterface::getColumnSpan,
|
||||
&AccessibilityTableInterface::Span::num);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ContainingGrid (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withTableInterface (pRetVal, [&] (const AccessibilityHandler& tableHandler)
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
tableHandler.getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetRowHeaderItems (SAFEARRAY**) override
|
||||
{
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetColumnHeaderItems (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return withTableInterface (pRetVal, [&] (const AccessibilityHandler& tableHandler)
|
||||
{
|
||||
if (auto* tableInterface = tableHandler.getTableInterface())
|
||||
{
|
||||
if (const auto column = tableInterface->getColumnSpan (getHandler()))
|
||||
{
|
||||
if (auto* header = tableInterface->getHeaderHandler())
|
||||
{
|
||||
const auto children = header->getChildren();
|
||||
|
||||
if (isPositiveAndBelow (column->begin, children.size()))
|
||||
{
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
|
||||
if (auto* child = children[(size_t) column->begin])
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
if (child->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress())) == S_OK && provider != nullptr)
|
||||
{
|
||||
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
|
||||
LONG index = 0;
|
||||
const auto hr = SafeArrayPutElement (*pRetVal, &index, provider);
|
||||
|
||||
return ! FAILED (hr);
|
||||
}
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
private:
|
||||
template <typename Value, typename Callback>
|
||||
JUCE_COMRESULT withTableInterface (Value* pRetVal, Callback&& callback) const
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* handler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&getHandler(), &AccessibilityHandler::getTableInterface))
|
||||
if (handler->getTableInterface() != nullptr && callback (*handler))
|
||||
return S_OK;
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT withTableSpan (int* pRetVal,
|
||||
Optional<AccessibilityTableInterface::Span> (AccessibilityTableInterface::* getSpan) (const AccessibilityHandler&) const,
|
||||
int AccessibilityTableInterface::Span::* spanMember) const
|
||||
{
|
||||
return withTableInterface (pRetVal, [&] (const AccessibilityHandler& handler)
|
||||
{
|
||||
if (const auto span = ((handler.getTableInterface())->*getSpan) (getHandler()))
|
||||
{
|
||||
*pRetVal = (*span).*spanMember;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridItemProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIAGridProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::IGridProvider, ComTypes::ITableProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT GetItem (int row, int column, IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
|
||||
{
|
||||
if (! isPositiveAndBelow (row, tableInterface.getNumRows())
|
||||
|| ! isPositiveAndBelow (column, tableInterface.getNumColumns()))
|
||||
return E_INVALIDARG;
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
if (auto* cellHandler = tableInterface.getCellHandler (row, column))
|
||||
{
|
||||
cellHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (auto* rowHandler = tableInterface.getRowHandler (row))
|
||||
{
|
||||
rowHandler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
return E_FAIL;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_RowCount (int* pRetVal) override
|
||||
{
|
||||
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
|
||||
{
|
||||
*pRetVal = tableInterface.getNumRows();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ColumnCount (int* pRetVal) override
|
||||
{
|
||||
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
|
||||
{
|
||||
*pRetVal = tableInterface.getNumColumns();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetRowHeaders (SAFEARRAY**) override
|
||||
{
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetColumnHeaders (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return withTableInterface (pRetVal, [&] (const AccessibilityTableInterface& tableInterface)
|
||||
{
|
||||
if (auto* header = tableInterface.getHeaderHandler())
|
||||
{
|
||||
const auto children = header->getChildren();
|
||||
|
||||
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, (ULONG) children.size());
|
||||
|
||||
LONG index = 0;
|
||||
|
||||
for (const auto& child : children)
|
||||
{
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
if (child != nullptr)
|
||||
child->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
if (provider == nullptr)
|
||||
return E_FAIL;
|
||||
|
||||
const auto hr = SafeArrayPutElement (*pRetVal, &index, provider);
|
||||
|
||||
if (FAILED (hr))
|
||||
return E_FAIL;
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_RowOrColumnMajor (ComTypes::RowOrColumnMajor* pRetVal) override
|
||||
{
|
||||
*pRetVal = ComTypes::RowOrColumnMajor_RowMajor;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Value, typename Callback>
|
||||
JUCE_COMRESULT withTableInterface (Value* pRetVal, Callback&& callback) const
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* tableHandler = detail::AccessibilityHelpers::getEnclosingHandlerWithInterface (&getHandler(), &AccessibilityHandler::getTableInterface))
|
||||
if (auto* tableInterface = tableHandler->getTableInterface())
|
||||
return callback (*tableInterface);
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace VariantHelpers
|
||||
{
|
||||
namespace Detail
|
||||
{
|
||||
template <typename Fn, typename ValueType>
|
||||
inline VARIANT getWithValueGeneric (Fn&& setter, ValueType value)
|
||||
{
|
||||
VARIANT result{};
|
||||
setter (value, &result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
inline void clear (VARIANT* variant)
|
||||
{
|
||||
variant->vt = VT_EMPTY;
|
||||
}
|
||||
|
||||
inline void setInt (int value, VARIANT* variant)
|
||||
{
|
||||
variant->vt = VT_I4;
|
||||
variant->lVal = value;
|
||||
}
|
||||
|
||||
inline void setBool (bool value, VARIANT* variant)
|
||||
{
|
||||
variant->vt = VT_BOOL;
|
||||
variant->boolVal = value ? -1 : 0;
|
||||
}
|
||||
|
||||
inline void setString (const String& value, VARIANT* variant)
|
||||
{
|
||||
variant->vt = VT_BSTR;
|
||||
variant->bstrVal = SysAllocString ((const OLECHAR*) value.toWideCharPointer());
|
||||
}
|
||||
|
||||
inline void setDouble (double value, VARIANT* variant)
|
||||
{
|
||||
variant->vt = VT_R8;
|
||||
variant->dblVal = value;
|
||||
}
|
||||
|
||||
inline VARIANT getWithValue (double value) { return Detail::getWithValueGeneric (&setDouble, value); }
|
||||
inline VARIANT getWithValue (const String& value) { return Detail::getWithValueGeneric (&setString, value); }
|
||||
}
|
||||
|
||||
inline JUCE_COMRESULT addHandlersToArray (const std::vector<const AccessibilityHandler*>& handlers, SAFEARRAY** pRetVal)
|
||||
{
|
||||
auto numHandlers = handlers.size();
|
||||
|
||||
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, (ULONG) numHandlers);
|
||||
|
||||
if (pRetVal != nullptr)
|
||||
{
|
||||
for (LONG i = 0; i < (LONG) numHandlers; ++i)
|
||||
{
|
||||
auto* handler = handlers[(size_t) i];
|
||||
|
||||
if (handler == nullptr)
|
||||
continue;
|
||||
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
auto hr = SafeArrayPutElement (*pRetVal, &i, provider);
|
||||
|
||||
if (FAILED (hr))
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
template <typename Value, typename Object, typename Callback>
|
||||
inline JUCE_COMRESULT withCheckedComArgs (Value* pRetVal, Object& handle, Callback&& callback)
|
||||
{
|
||||
if (pRetVal == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
*pRetVal = Value{};
|
||||
|
||||
if (! handle.isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
return callback();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIAInvokeProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::IInvokeProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Invoke() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (handler.getActions().invoke (AccessibilityActionType::press))
|
||||
{
|
||||
using namespace ComTypes::Constants;
|
||||
|
||||
if (isElementValid())
|
||||
sendAccessibilityAutomationEvent (handler, UIA_Invoke_InvokedEventId);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAInvokeProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIAProviderBase
|
||||
{
|
||||
public:
|
||||
explicit UIAProviderBase (AccessibilityNativeHandle* nativeHandleIn)
|
||||
: nativeHandle (nativeHandleIn)
|
||||
{
|
||||
}
|
||||
|
||||
bool isElementValid() const
|
||||
{
|
||||
if (nativeHandle != nullptr)
|
||||
return nativeHandle->isElementValid();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const AccessibilityHandler& getHandler() const
|
||||
{
|
||||
return nativeHandle->getHandler();
|
||||
}
|
||||
|
||||
private:
|
||||
ComSmartPtr<AccessibilityNativeHandle> nativeHandle;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAProviderBase)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
void sendAccessibilityAutomationEvent (const AccessibilityHandler&, EVENTID);
|
||||
void sendAccessibilityPropertyChangedEvent (const AccessibilityHandler&, PROPERTYID, VARIANT);
|
||||
} // namespace juce
|
||||
|
||||
#include "juce_UIAProviderBase_windows.h"
|
||||
#include "juce_UIAExpandCollapseProvider_windows.h"
|
||||
#include "juce_UIAGridItemProvider_windows.h"
|
||||
#include "juce_UIAGridProvider_windows.h"
|
||||
#include "juce_UIAInvokeProvider_windows.h"
|
||||
#include "juce_UIARangeValueProvider_windows.h"
|
||||
#include "juce_UIASelectionProvider_windows.h"
|
||||
#include "juce_UIATextProvider_windows.h"
|
||||
#include "juce_UIAToggleProvider_windows.h"
|
||||
#include "juce_UIATransformProvider_windows.h"
|
||||
#include "juce_UIAValueProvider_windows.h"
|
||||
#include "juce_UIAWindowProvider_windows.h"
|
||||
Vendored
+137
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIARangeValueProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::IRangeValueProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT SetValue (double val) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (auto* valueInterface = handler.getValueInterface())
|
||||
{
|
||||
auto range = valueInterface->getRange();
|
||||
|
||||
if (range.isValid())
|
||||
{
|
||||
if (val < range.getMinimumValue() || val > range.getMaximumValue())
|
||||
return E_INVALIDARG;
|
||||
|
||||
if (! valueInterface->isReadOnly())
|
||||
{
|
||||
valueInterface->setValue (val);
|
||||
|
||||
VARIANT newValue;
|
||||
VariantHelpers::setDouble (valueInterface->getCurrentValue(), &newValue);
|
||||
sendAccessibilityPropertyChangedEvent (handler, UIA_RangeValueValuePropertyId, newValue);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Value (double* pRetVal) override
|
||||
{
|
||||
return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface)
|
||||
{
|
||||
return valueInterface.getCurrentValue();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsReadOnly (BOOL* pRetVal) override
|
||||
{
|
||||
return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface)
|
||||
{
|
||||
return valueInterface.isReadOnly();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Maximum (double* pRetVal) override
|
||||
{
|
||||
return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface)
|
||||
{
|
||||
return valueInterface.getRange().getMaximumValue();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Minimum (double* pRetVal) override
|
||||
{
|
||||
return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface)
|
||||
{
|
||||
return valueInterface.getRange().getMinimumValue();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_LargeChange (double* pRetVal) override
|
||||
{
|
||||
return get_SmallChange (pRetVal);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_SmallChange (double* pRetVal) override
|
||||
{
|
||||
return withValueInterface (pRetVal, [] (const AccessibilityValueInterface& valueInterface)
|
||||
{
|
||||
return valueInterface.getRange().getInterval();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Value, typename Callback>
|
||||
JUCE_COMRESULT withValueInterface (Value* pRetVal, Callback&& callback) const
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* valueInterface = getHandler().getValueInterface())
|
||||
{
|
||||
if (valueInterface->getRange().isValid())
|
||||
{
|
||||
*pRetVal = callback (*valueInterface);
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIARangeValueProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+247
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
//==============================================================================
|
||||
class UIASelectionItemProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::ISelectionItemProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIASelectionItemProvider (AccessibilityNativeHandle* handle)
|
||||
: UIAProviderBase (handle),
|
||||
isRadioButton (getHandler().getRole() == AccessibilityRole::radioButton)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AddToSelection() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (isRadioButton)
|
||||
{
|
||||
using namespace ComTypes::Constants;
|
||||
|
||||
handler.getActions().invoke (AccessibilityActionType::press);
|
||||
sendAccessibilityAutomationEvent (handler, UIA_SelectionItem_ElementSelectedEventId);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
handler.getActions().invoke (AccessibilityActionType::toggle);
|
||||
handler.getActions().invoke (AccessibilityActionType::press);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsSelected (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
const auto state = getHandler().getCurrentState();
|
||||
*pRetVal = isRadioButton ? state.isChecked() : state.isSelected();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_SelectionContainer (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (! isRadioButton)
|
||||
if (auto* parent = getHandler().getParent())
|
||||
parent->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RemoveFromSelection() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (! isRadioButton)
|
||||
{
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (handler.getCurrentState().isSelected())
|
||||
getHandler().getActions().invoke (AccessibilityActionType::toggle);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Select() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
AddToSelection();
|
||||
|
||||
if (isElementValid() && ! isRadioButton)
|
||||
{
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (auto* parent = handler.getParent())
|
||||
for (auto* child : parent->getChildren())
|
||||
if (child != &handler && child->getCurrentState().isSelected())
|
||||
child->getActions().invoke (AccessibilityActionType::toggle);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
const bool isRadioButton;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIASelectionItemProvider)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class UIASelectionProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::ISelectionProvider2>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
|
||||
{
|
||||
if (iid == __uuidof (IUnknown) || iid == __uuidof (ComTypes::ISelectionProvider))
|
||||
return castToType<ComTypes::ISelectionProvider> (result);
|
||||
|
||||
if (iid == __uuidof (ComTypes::ISelectionProvider2))
|
||||
return castToType<ComTypes::ISelectionProvider2> (result);
|
||||
|
||||
*result = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT get_CanSelectMultiple (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = isMultiSelectable();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsSelectionRequired (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = getSelectedChildren().size() > 0 && ! isMultiSelectable();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetSelection (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
return addHandlersToArray (getSelectedChildren(), pRetVal);
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT get_FirstSelectedItem (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto selectedChildren = getSelectedChildren();
|
||||
|
||||
if (! selectedChildren.empty())
|
||||
selectedChildren.front()->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_LastSelectedItem (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto selectedChildren = getSelectedChildren();
|
||||
|
||||
if (! selectedChildren.empty())
|
||||
selectedChildren.back()->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CurrentSelectedItem (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
get_FirstSelectedItem (pRetVal);
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ItemCount (int* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = (int) getSelectedChildren().size();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
bool isMultiSelectable() const noexcept
|
||||
{
|
||||
return getHandler().getCurrentState().isMultiSelectable();
|
||||
}
|
||||
|
||||
std::vector<const AccessibilityHandler*> getSelectedChildren() const
|
||||
{
|
||||
std::vector<const AccessibilityHandler*> selectedHandlers;
|
||||
|
||||
for (auto* child : getHandler().getComponent().getChildren())
|
||||
if (auto* handler = child->getAccessibilityHandler())
|
||||
if (handler->getCurrentState().isSelected())
|
||||
selectedHandlers.push_back (handler);
|
||||
|
||||
return selectedHandlers;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIASelectionProvider)
|
||||
};
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+619
@@ -0,0 +1,619 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIATextProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::ITextProvider2>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
if (iid == __uuidof (IUnknown) || iid == __uuidof (ComTypes::ITextProvider))
|
||||
return castToType<ComTypes::ITextProvider> (result);
|
||||
|
||||
if (iid == __uuidof (ComTypes::ITextProvider2))
|
||||
return castToType<ComTypes::ITextProvider2> (result);
|
||||
|
||||
*result = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT get_DocumentRange (ComTypes::ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
*pRetVal = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() });
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_SupportedTextSelection (ComTypes::SupportedTextSelection* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = ComTypes::SupportedTextSelection_Single;
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetSelection (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
|
||||
|
||||
if (pRetVal != nullptr)
|
||||
{
|
||||
auto selection = textInterface.getSelection();
|
||||
auto hasSelection = ! selection.isEmpty();
|
||||
auto cursorPos = textInterface.getTextInsertionOffset();
|
||||
|
||||
auto* rangeProvider = new UIATextRangeProvider (*this,
|
||||
{ hasSelection ? selection.getStart() : cursorPos,
|
||||
hasSelection ? selection.getEnd() : cursorPos });
|
||||
|
||||
LONG pos = 0;
|
||||
auto hr = SafeArrayPutElement (*pRetVal, &pos, static_cast<IUnknown*> (rangeProvider));
|
||||
|
||||
if (FAILED (hr))
|
||||
return E_FAIL;
|
||||
|
||||
rangeProvider->Release();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetVisibleRanges (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 1);
|
||||
|
||||
if (pRetVal != nullptr)
|
||||
{
|
||||
auto* rangeProvider = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() });
|
||||
|
||||
LONG pos = 0;
|
||||
auto hr = SafeArrayPutElement (*pRetVal, &pos, static_cast<IUnknown*> (rangeProvider));
|
||||
|
||||
if (FAILED (hr))
|
||||
return E_FAIL;
|
||||
|
||||
rangeProvider->Release();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RangeFromChild (IRawElementProviderSimple*, ComTypes::ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RangeFromPoint (ComTypes::UiaPoint point, ComTypes::ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
auto offset = textInterface.getOffsetAtPoint ({ roundToInt (point.x), roundToInt (point.y) });
|
||||
|
||||
if (offset > 0)
|
||||
*pRetVal = new UIATextRangeProvider (*this, { offset, offset });
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT GetCaretRange (BOOL* isActive, ComTypes::ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
*isActive = getHandler().hasFocus (false);
|
||||
|
||||
auto cursorPos = textInterface.getTextInsertionOffset();
|
||||
*pRetVal = new UIATextRangeProvider (*this, { cursorPos, cursorPos });
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RangeFromAnnotation (IRawElementProviderSimple*, ComTypes::ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
template <typename Value, typename Callback>
|
||||
JUCE_COMRESULT withTextInterface (Value* pRetVal, Callback&& callback) const
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* textInterface = getHandler().getTextInterface())
|
||||
return callback (*textInterface);
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class UIATextRangeProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::ITextRangeProvider>
|
||||
{
|
||||
public:
|
||||
UIATextRangeProvider (UIATextProvider& textProvider, Range<int> range)
|
||||
: UIAProviderBase (textProvider.getHandler().getNativeImplementation()),
|
||||
owner (&textProvider),
|
||||
selectionRange (range)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Range<int> getSelectionRange() const noexcept { return selectionRange; }
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AddToSelection() override
|
||||
{
|
||||
return Select();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Clone (ComTypes::ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = new UIATextRangeProvider (*owner, selectionRange);
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Compare (ComTypes::ITextRangeProvider* range, BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = (selectionRange == static_cast<UIATextRangeProvider*> (range)->getSelectionRange());
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT CompareEndpoints (ComTypes::TextPatternRangeEndpoint endpoint,
|
||||
ComTypes::ITextRangeProvider* targetRange,
|
||||
ComTypes::TextPatternRangeEndpoint targetEndpoint,
|
||||
int* pRetVal) override
|
||||
{
|
||||
if (targetRange == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto offset = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart()
|
||||
: selectionRange.getEnd());
|
||||
|
||||
auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
|
||||
auto otherOffset = (targetEndpoint == ComTypes::TextPatternRangeEndpoint_Start ? otherRange.getStart()
|
||||
: otherRange.getEnd());
|
||||
|
||||
*pRetVal = offset - otherOffset;
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT ExpandToEnclosingUnit (ComTypes::TextUnit unit) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* textInterface = owner->getHandler().getTextInterface())
|
||||
{
|
||||
using ATH = AccessibilityTextHelpers;
|
||||
|
||||
const auto boundaryType = getBoundaryType (unit);
|
||||
const auto start = ATH::findTextBoundary (*textInterface,
|
||||
selectionRange.getStart(),
|
||||
boundaryType,
|
||||
ATH::Direction::backwards,
|
||||
ATH::IncludeThisBoundary::yes,
|
||||
ATH::IncludeWhitespaceAfterWords::no);
|
||||
|
||||
const auto end = ATH::findTextBoundary (*textInterface,
|
||||
start,
|
||||
boundaryType,
|
||||
ATH::Direction::forwards,
|
||||
ATH::IncludeThisBoundary::no,
|
||||
ATH::IncludeWhitespaceAfterWords::yes);
|
||||
|
||||
selectionRange = Range<int> (start, end);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT FindAttribute (TEXTATTRIBUTEID, VARIANT, BOOL, ComTypes::ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT FindText (BSTR text, BOOL backward, BOOL ignoreCase,
|
||||
ComTypes::ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
auto selectionText = textInterface.getText (selectionRange);
|
||||
String textToSearchFor (text);
|
||||
|
||||
auto offset = (backward ? (ignoreCase ? selectionText.lastIndexOfIgnoreCase (textToSearchFor) : selectionText.lastIndexOf (textToSearchFor))
|
||||
: (ignoreCase ? selectionText.indexOfIgnoreCase (textToSearchFor) : selectionText.indexOf (textToSearchFor)));
|
||||
|
||||
if (offset != -1)
|
||||
*pRetVal = new UIATextRangeProvider (*owner, { offset, offset + textToSearchFor.length() });
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetAttributeValue (TEXTATTRIBUTEID attributeId, VARIANT* pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
VariantHelpers::clear (pRetVal);
|
||||
|
||||
using namespace ComTypes::Constants;
|
||||
|
||||
switch (attributeId)
|
||||
{
|
||||
case UIA_IsReadOnlyAttributeId:
|
||||
{
|
||||
VariantHelpers::setBool (textInterface.isReadOnly(), pRetVal);
|
||||
break;
|
||||
}
|
||||
|
||||
case UIA_CaretPositionAttributeId:
|
||||
{
|
||||
auto cursorPos = textInterface.getTextInsertionOffset();
|
||||
|
||||
auto caretPos = [&]
|
||||
{
|
||||
if (cursorPos == 0)
|
||||
return ComTypes::CaretPosition_BeginningOfLine;
|
||||
|
||||
if (cursorPos == textInterface.getTotalNumCharacters())
|
||||
return ComTypes::CaretPosition_EndOfLine;
|
||||
|
||||
return ComTypes::CaretPosition_Unknown;
|
||||
}();
|
||||
|
||||
VariantHelpers::setInt (caretPos, pRetVal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetBoundingRectangles (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
auto rectangleList = textInterface.getTextBounds (selectionRange);
|
||||
auto numRectangles = rectangleList.getNumRectangles();
|
||||
|
||||
*pRetVal = SafeArrayCreateVector (VT_R8, 0, 4 * (ULONG) numRectangles);
|
||||
|
||||
if (*pRetVal == nullptr)
|
||||
return E_FAIL;
|
||||
|
||||
if (numRectangles > 0)
|
||||
{
|
||||
double* doubleArr = nullptr;
|
||||
|
||||
if (FAILED (SafeArrayAccessData (*pRetVal, reinterpret_cast<void**> (&doubleArr))))
|
||||
{
|
||||
SafeArrayDestroy (*pRetVal);
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < numRectangles; ++i)
|
||||
{
|
||||
auto r = Desktop::getInstance().getDisplays().logicalToPhysical (rectangleList.getRectangle (i));
|
||||
|
||||
doubleArr[i * 4] = r.getX();
|
||||
doubleArr[i * 4 + 1] = r.getY();
|
||||
doubleArr[i * 4 + 2] = r.getWidth();
|
||||
doubleArr[i * 4 + 3] = r.getHeight();
|
||||
}
|
||||
|
||||
if (FAILED (SafeArrayUnaccessData (*pRetVal)))
|
||||
{
|
||||
SafeArrayDestroy (*pRetVal);
|
||||
return E_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetChildren (SAFEARRAY** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = SafeArrayCreateVector (VT_UNKNOWN, 0, 0);
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetEnclosingElement (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
getHandler().getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
return S_OK;
|
||||
});
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetText (int maxLength, BSTR* pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
auto text = textInterface.getText (selectionRange);
|
||||
|
||||
if (maxLength >= 0 && text.length() > maxLength)
|
||||
text = text.substring (0, maxLength);
|
||||
|
||||
*pRetVal = SysAllocString ((const OLECHAR*) text.toWideCharPointer());
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Move (ComTypes::TextUnit unit, int count, int* pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
using ATH = AccessibilityTextHelpers;
|
||||
|
||||
const auto boundaryType = getBoundaryType (unit);
|
||||
const auto previousUnitBoundary = ATH::findTextBoundary (textInterface,
|
||||
selectionRange.getStart(),
|
||||
boundaryType,
|
||||
ATH::Direction::backwards,
|
||||
ATH::IncludeThisBoundary::yes,
|
||||
ATH::IncludeWhitespaceAfterWords::no);
|
||||
|
||||
auto numMoved = 0;
|
||||
auto movedEndpoint = previousUnitBoundary;
|
||||
|
||||
for (; numMoved < std::abs (count); ++numMoved)
|
||||
{
|
||||
const auto nextEndpoint = ATH::findTextBoundary (textInterface,
|
||||
movedEndpoint,
|
||||
boundaryType,
|
||||
count > 0 ? ATH::Direction::forwards : ATH::Direction::backwards,
|
||||
ATH::IncludeThisBoundary::no,
|
||||
count > 0 ? ATH::IncludeWhitespaceAfterWords::yes : ATH::IncludeWhitespaceAfterWords::no);
|
||||
|
||||
if (nextEndpoint == movedEndpoint)
|
||||
break;
|
||||
|
||||
movedEndpoint = nextEndpoint;
|
||||
}
|
||||
|
||||
*pRetVal = numMoved;
|
||||
|
||||
ExpandToEnclosingUnit (unit);
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT MoveEndpointByRange (ComTypes::TextPatternRangeEndpoint endpoint,
|
||||
ComTypes::ITextRangeProvider* targetRange,
|
||||
ComTypes::TextPatternRangeEndpoint targetEndpoint) override
|
||||
{
|
||||
if (targetRange == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (owner->getHandler().getTextInterface() != nullptr)
|
||||
{
|
||||
auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
|
||||
auto targetPoint = (targetEndpoint == ComTypes::TextPatternRangeEndpoint_Start ? otherRange.getStart()
|
||||
: otherRange.getEnd());
|
||||
|
||||
setEndpointChecked (endpoint, targetPoint);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT MoveEndpointByUnit (ComTypes::TextPatternRangeEndpoint endpoint,
|
||||
ComTypes::TextUnit unit,
|
||||
int count,
|
||||
int* pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
if (count == 0 || textInterface.getTotalNumCharacters() == 0)
|
||||
return S_OK;
|
||||
|
||||
const auto endpointToMove = (endpoint == ComTypes::TextPatternRangeEndpoint_Start ? selectionRange.getStart()
|
||||
: selectionRange.getEnd());
|
||||
|
||||
using ATH = AccessibilityTextHelpers;
|
||||
|
||||
const auto direction = (count > 0 ? ATH::Direction::forwards
|
||||
: ATH::Direction::backwards);
|
||||
|
||||
const auto boundaryType = getBoundaryType (unit);
|
||||
auto movedEndpoint = endpointToMove;
|
||||
|
||||
int numMoved = 0;
|
||||
for (; numMoved < std::abs (count); ++numMoved)
|
||||
{
|
||||
auto nextEndpoint = ATH::findTextBoundary (textInterface,
|
||||
movedEndpoint,
|
||||
boundaryType,
|
||||
direction,
|
||||
ATH::IncludeThisBoundary::no,
|
||||
direction == ATH::Direction::forwards ? ATH::IncludeWhitespaceAfterWords::yes
|
||||
: ATH::IncludeWhitespaceAfterWords::no);
|
||||
|
||||
if (nextEndpoint == movedEndpoint)
|
||||
break;
|
||||
|
||||
movedEndpoint = nextEndpoint;
|
||||
}
|
||||
|
||||
*pRetVal = numMoved;
|
||||
|
||||
setEndpointChecked (endpoint, movedEndpoint);
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RemoveFromSelection() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* textInterface = owner->getHandler().getTextInterface())
|
||||
{
|
||||
textInterface->setSelection ({});
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT ScrollIntoView (BOOL) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Select() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* textInterface = owner->getHandler().getTextInterface())
|
||||
{
|
||||
textInterface->setSelection ({});
|
||||
textInterface->setSelection (selectionRange);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
private:
|
||||
static AccessibilityTextHelpers::BoundaryType getBoundaryType (ComTypes::TextUnit unit)
|
||||
{
|
||||
switch (unit)
|
||||
{
|
||||
case ComTypes::TextUnit_Character:
|
||||
return AccessibilityTextHelpers::BoundaryType::character;
|
||||
|
||||
case ComTypes::TextUnit_Format:
|
||||
case ComTypes::TextUnit_Word:
|
||||
return AccessibilityTextHelpers::BoundaryType::word;
|
||||
|
||||
case ComTypes::TextUnit_Line:
|
||||
return AccessibilityTextHelpers::BoundaryType::line;
|
||||
|
||||
case ComTypes::TextUnit_Paragraph:
|
||||
case ComTypes::TextUnit_Page:
|
||||
case ComTypes::TextUnit_Document:
|
||||
return AccessibilityTextHelpers::BoundaryType::document;
|
||||
};
|
||||
|
||||
jassertfalse;
|
||||
return AccessibilityTextHelpers::BoundaryType::character;
|
||||
}
|
||||
|
||||
void setEndpointChecked (ComTypes::TextPatternRangeEndpoint endpoint, int newEndpoint)
|
||||
{
|
||||
if (endpoint == ComTypes::TextPatternRangeEndpoint_Start)
|
||||
{
|
||||
if (selectionRange.getEnd() < newEndpoint)
|
||||
selectionRange.setEnd (newEndpoint);
|
||||
|
||||
selectionRange.setStart (newEndpoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (selectionRange.getStart() > newEndpoint)
|
||||
selectionRange.setStart (newEndpoint);
|
||||
|
||||
selectionRange.setEnd (newEndpoint);
|
||||
}
|
||||
}
|
||||
|
||||
ComSmartPtr<UIATextProvider> owner;
|
||||
Range<int> selectionRange;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextRangeProvider)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATextProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIAToggleProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::IToggleProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Toggle() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (handler.getActions().invoke (AccessibilityActionType::toggle)
|
||||
|| handler.getActions().invoke (AccessibilityActionType::press))
|
||||
{
|
||||
VARIANT newValue;
|
||||
VariantHelpers::setInt (getCurrentToggleState(), &newValue);
|
||||
|
||||
sendAccessibilityPropertyChangedEvent (handler, UIA_ToggleToggleStatePropertyId, newValue);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ToggleState (ComTypes::ToggleState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = getCurrentToggleState();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
ComTypes::ToggleState getCurrentToggleState() const
|
||||
{
|
||||
return getHandler().getCurrentState().isChecked() ? ComTypes::ToggleState_On
|
||||
: ComTypes::ToggleState_Off;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAToggleProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+122
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIATransformProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::ITransformProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Move (double x, double y) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
RECT rect;
|
||||
GetWindowRect ((HWND) peer->getNativeHandle(), &rect);
|
||||
|
||||
rect.left = roundToInt (x);
|
||||
rect.top = roundToInt (y);
|
||||
|
||||
auto bounds = Rectangle<int>::leftTopRightBottom (rect.left, rect.top, rect.right, rect.bottom);
|
||||
|
||||
peer->setBounds (Desktop::getInstance().getDisplays().physicalToLogical (bounds),
|
||||
peer->isFullScreen());
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Resize (double width, double height) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
auto scale = peer->getPlatformScaleFactor();
|
||||
|
||||
peer->getComponent().setSize (roundToInt (width / scale),
|
||||
roundToInt (height / scale));
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Rotate (double) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CanMove (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = true;
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CanResize (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
*pRetVal = ((peer->getStyleFlags() & ComponentPeer::windowIsResizable) != 0);
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CanRotate (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = false;
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
ComponentPeer* getPeer() const
|
||||
{
|
||||
return getHandler().getComponent().getPeer();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIATransformProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIAValueProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::IValueProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT SetValue (LPCWSTR val) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
auto& valueInterface = *handler.getValueInterface();
|
||||
|
||||
if (valueInterface.isReadOnly())
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
|
||||
valueInterface.setValueAsString (String (val));
|
||||
|
||||
VARIANT newValue;
|
||||
VariantHelpers::setString (valueInterface.getCurrentValueAsString(), &newValue);
|
||||
|
||||
sendAccessibilityPropertyChangedEvent (handler, UIA_ValueValuePropertyId, newValue);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Value (BSTR* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto currentValueString = getHandler().getValueInterface()->getCurrentValueAsString();
|
||||
|
||||
*pRetVal = SysAllocString ((const OLECHAR*) currentValueString.toWideCharPointer());
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsReadOnly (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = getHandler().getValueInterface()->isReadOnly();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAValueProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+194
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class UIAWindowProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ComTypes::IWindowProvider>
|
||||
{
|
||||
public:
|
||||
using UIAProviderBase::UIAProviderBase;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT SetVisualState (ComTypes::WindowVisualState state) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ComTypes::WindowVisualState_Maximized:
|
||||
peer->setFullScreen (true);
|
||||
break;
|
||||
|
||||
case ComTypes::WindowVisualState_Minimized:
|
||||
peer->setMinimised (true);
|
||||
break;
|
||||
|
||||
case ComTypes::WindowVisualState_Normal:
|
||||
peer->setFullScreen (false);
|
||||
peer->setMinimised (false);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Close() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
peer->handleUserClosingWindow();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT WaitForInputIdle (int, BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CanMaximize (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = (peer->getStyleFlags() & ComponentPeer::windowHasMaximiseButton) != 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_CanMinimize (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = (peer->getStyleFlags() & ComponentPeer::windowHasMinimiseButton) != 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsModal (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = peer->getComponent().isCurrentlyModal();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_WindowVisualState (ComTypes::WindowVisualState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
if (peer->isFullScreen())
|
||||
*pRetVal = ComTypes::WindowVisualState_Maximized;
|
||||
else if (peer->isMinimised())
|
||||
*pRetVal = ComTypes::WindowVisualState_Minimized;
|
||||
else
|
||||
*pRetVal = ComTypes::WindowVisualState_Normal;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_WindowInteractionState (ComTypes::WindowInteractionState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = peer->getComponent().isCurrentlyBlockedByAnotherModalComponent()
|
||||
? ComTypes::WindowInteractionState::WindowInteractionState_BlockedByModalWindow
|
||||
: ComTypes::WindowInteractionState::WindowInteractionState_Running;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_IsTopmost (BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = peer->isFocused();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
ComponentPeer* getPeer() const
|
||||
{
|
||||
return getHandler().getComponent().getPeer();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAWindowProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+160
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class WindowsUIAWrapper : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
bool isLoaded() const noexcept
|
||||
{
|
||||
return uiaReturnRawElementProvider != nullptr
|
||||
&& uiaHostProviderFromHwnd != nullptr
|
||||
&& uiaRaiseAutomationPropertyChangedEvent != nullptr
|
||||
&& uiaRaiseAutomationEvent != nullptr
|
||||
&& uiaClientsAreListening != nullptr
|
||||
&& uiaDisconnectProvider != nullptr
|
||||
&& uiaDisconnectAllProviders != nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
LRESULT returnRawElementProvider (HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple* provider)
|
||||
{
|
||||
return uiaReturnRawElementProvider != nullptr ? uiaReturnRawElementProvider (hwnd, wParam, lParam, provider)
|
||||
: (LRESULT) nullptr;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT hostProviderFromHwnd (HWND hwnd, IRawElementProviderSimple** provider)
|
||||
{
|
||||
return uiaHostProviderFromHwnd != nullptr ? uiaHostProviderFromHwnd (hwnd, provider)
|
||||
: (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT raiseAutomationPropertyChangedEvent (IRawElementProviderSimple* provider, PROPERTYID propID, VARIANT oldValue, VARIANT newValue)
|
||||
{
|
||||
return uiaRaiseAutomationPropertyChangedEvent != nullptr ? uiaRaiseAutomationPropertyChangedEvent (provider, propID, oldValue, newValue)
|
||||
: (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT raiseAutomationEvent (IRawElementProviderSimple* provider, EVENTID eventID)
|
||||
{
|
||||
return uiaRaiseAutomationEvent != nullptr ? uiaRaiseAutomationEvent (provider, eventID)
|
||||
: (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
BOOL clientsAreListening()
|
||||
{
|
||||
return uiaClientsAreListening != nullptr ? uiaClientsAreListening()
|
||||
: false;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT disconnectProvider (IRawElementProviderSimple* provider)
|
||||
{
|
||||
if (uiaDisconnectProvider != nullptr)
|
||||
{
|
||||
const ScopedValueSetter<IRawElementProviderSimple*> disconnectingProviderSetter (disconnectingProvider, provider);
|
||||
return uiaDisconnectProvider (provider);
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT disconnectAllProviders()
|
||||
{
|
||||
if (uiaDisconnectAllProviders != nullptr)
|
||||
{
|
||||
const ScopedValueSetter<bool> disconnectingAllProvidersSetter (disconnectingAllProviders, true);
|
||||
return uiaDisconnectAllProviders();
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool isProviderDisconnecting (IRawElementProviderSimple* provider)
|
||||
{
|
||||
return disconnectingProvider == provider || disconnectingAllProviders;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (WindowsUIAWrapper)
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
WindowsUIAWrapper()
|
||||
{
|
||||
// force UIA COM library initialisation here to prevent an exception when calling methods from SendMessage()
|
||||
if (isLoaded())
|
||||
returnRawElementProvider (nullptr, 0, 0, nullptr);
|
||||
else
|
||||
jassertfalse; // UIAutomationCore could not be loaded!
|
||||
}
|
||||
|
||||
~WindowsUIAWrapper()
|
||||
{
|
||||
disconnectAllProviders();
|
||||
|
||||
if (uiaHandle != nullptr)
|
||||
::FreeLibrary (uiaHandle);
|
||||
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename FuncType>
|
||||
static FuncType getUiaFunction (HMODULE module, LPCSTR funcName)
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type")
|
||||
return (FuncType) GetProcAddress (module, funcName);
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
using UiaReturnRawElementProviderFunc = LRESULT (WINAPI*) (HWND, WPARAM, LPARAM, IRawElementProviderSimple*);
|
||||
using UiaHostProviderFromHwndFunc = HRESULT (WINAPI*) (HWND, IRawElementProviderSimple**);
|
||||
using UiaRaiseAutomationPropertyChangedEventFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*, PROPERTYID, VARIANT, VARIANT);
|
||||
using UiaRaiseAutomationEventFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*, EVENTID);
|
||||
using UiaClientsAreListeningFunc = BOOL (WINAPI*) ();
|
||||
using UiaDisconnectProviderFunc = HRESULT (WINAPI*) (IRawElementProviderSimple*);
|
||||
using UiaDisconnectAllProvidersFunc = HRESULT (WINAPI*) ();
|
||||
|
||||
HMODULE uiaHandle = ::LoadLibraryA ("UIAutomationCore.dll");
|
||||
UiaReturnRawElementProviderFunc uiaReturnRawElementProvider = getUiaFunction<UiaReturnRawElementProviderFunc> (uiaHandle, "UiaReturnRawElementProvider");
|
||||
UiaHostProviderFromHwndFunc uiaHostProviderFromHwnd = getUiaFunction<UiaHostProviderFromHwndFunc> (uiaHandle, "UiaHostProviderFromHwnd");
|
||||
UiaRaiseAutomationPropertyChangedEventFunc uiaRaiseAutomationPropertyChangedEvent = getUiaFunction<UiaRaiseAutomationPropertyChangedEventFunc> (uiaHandle, "UiaRaiseAutomationPropertyChangedEvent");
|
||||
UiaRaiseAutomationEventFunc uiaRaiseAutomationEvent = getUiaFunction<UiaRaiseAutomationEventFunc> (uiaHandle, "UiaRaiseAutomationEvent");
|
||||
UiaClientsAreListeningFunc uiaClientsAreListening = getUiaFunction<UiaClientsAreListeningFunc> (uiaHandle, "UiaClientsAreListening");
|
||||
UiaDisconnectProviderFunc uiaDisconnectProvider = getUiaFunction<UiaDisconnectProviderFunc> (uiaHandle, "UiaDisconnectProvider");
|
||||
UiaDisconnectAllProvidersFunc uiaDisconnectAllProviders = getUiaFunction<UiaDisconnectAllProvidersFunc> (uiaHandle, "UiaDisconnectAllProviders");
|
||||
|
||||
IRawElementProviderSimple* disconnectingProvider = nullptr;
|
||||
bool disconnectingAllProviders = false;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsUIAWrapper)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+978
@@ -0,0 +1,978 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.text.Selection;
|
||||
import android.text.SpanWatcher;
|
||||
import android.text.Spannable;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Pair;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.view.Choreographer;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.accessibility.AccessibilityNodeProvider;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class ComponentPeerView extends ViewGroup
|
||||
implements View.OnFocusChangeListener, Application.ActivityLifecycleCallbacks, Choreographer.FrameCallback
|
||||
{
|
||||
public ComponentPeerView (Context context, boolean opaque_, long host)
|
||||
{
|
||||
super (context);
|
||||
|
||||
if (Application.class.isInstance (context))
|
||||
{
|
||||
((Application) context).registerActivityLifecycleCallbacks (this);
|
||||
}
|
||||
else
|
||||
{
|
||||
((Application) context.getApplicationContext()).registerActivityLifecycleCallbacks (this);
|
||||
}
|
||||
|
||||
this.host = host;
|
||||
setWillNotDraw (false);
|
||||
opaque = opaque_;
|
||||
|
||||
setFocusable (true);
|
||||
setFocusableInTouchMode (true);
|
||||
setOnFocusChangeListener (this);
|
||||
|
||||
// swap red and blue colours to match internal opengl texture format
|
||||
ColorMatrix colorMatrix = new ColorMatrix();
|
||||
|
||||
float[] colorTransform = {0, 0, 1.0f, 0, 0,
|
||||
0, 1.0f, 0, 0, 0,
|
||||
1.0f, 0, 0, 0, 0,
|
||||
0, 0, 0, 1.0f, 0};
|
||||
|
||||
colorMatrix.set (colorTransform);
|
||||
paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));
|
||||
|
||||
java.lang.reflect.Method method = null;
|
||||
|
||||
try
|
||||
{
|
||||
method = getClass().getMethod ("setLayerType", int.class, Paint.class);
|
||||
}
|
||||
catch (SecurityException e)
|
||||
{
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
}
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
int layerTypeNone = 0;
|
||||
method.invoke (this, layerTypeNone, null);
|
||||
}
|
||||
catch (java.lang.IllegalArgumentException e)
|
||||
{
|
||||
}
|
||||
catch (java.lang.IllegalAccessException e)
|
||||
{
|
||||
}
|
||||
catch (java.lang.reflect.InvocationTargetException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Choreographer.getInstance().postFrameCallback (this);
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
host = 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
private native void handlePaint (long host, Canvas canvas, Paint paint);
|
||||
|
||||
@Override
|
||||
public void onDraw (Canvas canvas)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
handlePaint (host, canvas, paint);
|
||||
}
|
||||
|
||||
private native void handleDoFrame (long host, long frameTimeNanos);
|
||||
|
||||
@Override
|
||||
public void doFrame (long frameTimeNanos)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
handleDoFrame (host, frameTimeNanos);
|
||||
|
||||
Choreographer.getInstance().postFrameCallback (this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaque()
|
||||
{
|
||||
return opaque;
|
||||
}
|
||||
|
||||
private final boolean opaque;
|
||||
private long host;
|
||||
private final Paint paint = new Paint();
|
||||
|
||||
//==============================================================================
|
||||
private native void handleMouseDown (long host, int index, float x, float y, long time);
|
||||
private native void handleMouseDrag (long host, int index, float x, float y, long time);
|
||||
private native void handleMouseUp (long host, int index, float x, float y, long time);
|
||||
private native void handleAccessibilityHover (long host, int action, float x, float y, long time);
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent (MotionEvent event)
|
||||
{
|
||||
if (host == 0)
|
||||
return false;
|
||||
|
||||
int action = event.getAction();
|
||||
long time = event.getEventTime();
|
||||
|
||||
switch (action & MotionEvent.ACTION_MASK)
|
||||
{
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
handleMouseDown (host, event.getPointerId (0), event.getRawX(), event.getRawY(), time);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
handleMouseUp (host, event.getPointerId (0), event.getRawX(), event.getRawY(), time);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
{
|
||||
handleMouseDrag (host, event.getPointerId (0), event.getRawX(), event.getRawY(), time);
|
||||
|
||||
int n = event.getPointerCount();
|
||||
|
||||
if (n > 1)
|
||||
{
|
||||
int point[] = new int[2];
|
||||
getLocationOnScreen (point);
|
||||
|
||||
for (int i = 1; i < n; ++i)
|
||||
handleMouseDrag (host, event.getPointerId (i), event.getX (i) + point[0], event.getY (i) + point[1], time);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
{
|
||||
int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
handleMouseUp (host, event.getPointerId (0), event.getRawX(), event.getRawY(), time);
|
||||
}
|
||||
else
|
||||
{
|
||||
int point[] = new int[2];
|
||||
getLocationOnScreen (point);
|
||||
|
||||
handleMouseUp (host, event.getPointerId (i), event.getX (i) + point[0], event.getY (i) + point[1], time);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
{
|
||||
int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
handleMouseDown (host, event.getPointerId (0), event.getRawX(), event.getRawY(), time);
|
||||
}
|
||||
else
|
||||
{
|
||||
int point[] = new int[2];
|
||||
getLocationOnScreen (point);
|
||||
|
||||
handleMouseDown (host, event.getPointerId (i), event.getX (i) + point[0], event.getY (i) + point[1], time);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHoverEvent (MotionEvent event)
|
||||
{
|
||||
if (accessibilityManager.isTouchExplorationEnabled())
|
||||
{
|
||||
handleAccessibilityHover (host, event.getActionMasked(), event.getRawX(), event.getRawY(), event.getEventTime());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public static class TextInputTarget
|
||||
{
|
||||
public TextInputTarget (long owner) { host = owner; }
|
||||
|
||||
public boolean isTextInputActive() { return ComponentPeerView.textInputTargetIsTextInputActive (host); }
|
||||
public int getHighlightedRegionBegin() { return ComponentPeerView.textInputTargetGetHighlightedRegionBegin (host); }
|
||||
public int getHighlightedRegionEnd() { return ComponentPeerView.textInputTargetGetHighlightedRegionEnd (host); }
|
||||
public void setHighlightedRegion (int b, int e) { ComponentPeerView.textInputTargetSetHighlightedRegion (host, b, e); }
|
||||
public String getTextInRange (int b, int e) { return ComponentPeerView.textInputTargetGetTextInRange (host, b, e); }
|
||||
public void insertTextAtCaret (String text) { ComponentPeerView.textInputTargetInsertTextAtCaret (host, text); }
|
||||
public int getCaretPosition() { return ComponentPeerView.textInputTargetGetCaretPosition (host); }
|
||||
public int getTotalNumChars() { return ComponentPeerView.textInputTargetGetTotalNumChars (host); }
|
||||
public int getCharIndexForPoint (Point point) { return ComponentPeerView.textInputTargetGetCharIndexForPoint (host, point); }
|
||||
public int getKeyboardType() { return ComponentPeerView.textInputTargetGetKeyboardType (host); }
|
||||
public void setTemporaryUnderlining (List<Pair<Integer, Integer>> list) { ComponentPeerView.textInputTargetSetTemporaryUnderlining (host, list); }
|
||||
|
||||
//==============================================================================
|
||||
private final long host;
|
||||
}
|
||||
|
||||
private native static boolean textInputTargetIsTextInputActive (long host);
|
||||
private native static int textInputTargetGetHighlightedRegionBegin (long host);
|
||||
private native static int textInputTargetGetHighlightedRegionEnd (long host);
|
||||
private native static void textInputTargetSetHighlightedRegion (long host, int begin, int end);
|
||||
private native static String textInputTargetGetTextInRange (long host, int begin, int end);
|
||||
private native static void textInputTargetInsertTextAtCaret (long host, String text);
|
||||
private native static int textInputTargetGetCaretPosition (long host);
|
||||
private native static int textInputTargetGetTotalNumChars (long host);
|
||||
private native static int textInputTargetGetCharIndexForPoint (long host, Point point);
|
||||
private native static int textInputTargetGetKeyboardType (long host);
|
||||
private native static void textInputTargetSetTemporaryUnderlining (long host, List<Pair<Integer, Integer>> list);
|
||||
|
||||
private native long getFocusedTextInputTargetPointer (long host);
|
||||
|
||||
private TextInputTarget getFocusedTextInputTarget (long host)
|
||||
{
|
||||
final long ptr = getFocusedTextInputTargetPointer (host);
|
||||
return ptr != 0 ? new TextInputTarget (ptr) : null;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
private native void handleKeyDown (long host, int keycode, int textchar, int kbFlags);
|
||||
private native void handleKeyUp (long host, int keycode, int textchar);
|
||||
private native void handleBackButton (long host);
|
||||
private native void handleKeyboardHidden (long host);
|
||||
|
||||
private static int getInputTypeForJuceVirtualKeyboardType (int type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case 0: // textKeyboard
|
||||
return InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_VARIATION_NORMAL
|
||||
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
|
||||
case 1: // numericKeyboard
|
||||
return InputType.TYPE_CLASS_NUMBER
|
||||
| InputType.TYPE_NUMBER_VARIATION_NORMAL;
|
||||
case 2: // decimalKeyboard
|
||||
return InputType.TYPE_CLASS_NUMBER
|
||||
| InputType.TYPE_NUMBER_VARIATION_NORMAL
|
||||
| InputType.TYPE_NUMBER_FLAG_DECIMAL;
|
||||
case 3: // urlKeyboard
|
||||
return InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_VARIATION_URI
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
|
||||
case 4: // emailAddressKeyboard
|
||||
return InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
|
||||
case 5: // phoneNumberKeyboard
|
||||
return InputType.TYPE_CLASS_PHONE;
|
||||
case 6: // passwordKeyboard
|
||||
return InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
InputMethodManager getInputMethodManager()
|
||||
{
|
||||
return (InputMethodManager) getContext().getSystemService (Context.INPUT_METHOD_SERVICE);
|
||||
}
|
||||
|
||||
public void closeInputMethodContext()
|
||||
{
|
||||
InputMethodManager imm = getInputMethodManager();
|
||||
|
||||
if (imm == null)
|
||||
return;
|
||||
|
||||
if (cachedConnection != null)
|
||||
cachedConnection.closeConnection();
|
||||
|
||||
imm.restartInput (this);
|
||||
}
|
||||
|
||||
public void showKeyboard (int virtualKeyboardType, int selectionStart, int selectionEnd)
|
||||
{
|
||||
InputMethodManager imm = getInputMethodManager();
|
||||
|
||||
if (imm == null)
|
||||
return;
|
||||
|
||||
// restartingInput causes a call back to onCreateInputConnection, where we'll pick
|
||||
// up the correct keyboard characteristics to use for the focused TextInputTarget.
|
||||
imm.restartInput (this);
|
||||
imm.showSoftInput (this, 0);
|
||||
keyboardDismissListener.startListening();
|
||||
}
|
||||
|
||||
public void hideKeyboard()
|
||||
{
|
||||
InputMethodManager imm = getInputMethodManager();
|
||||
|
||||
if (imm == null)
|
||||
return;
|
||||
|
||||
imm.hideSoftInputFromWindow (getWindowToken(), 0);
|
||||
keyboardDismissListener.stopListening();
|
||||
}
|
||||
|
||||
public void backButtonPressed()
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
handleBackButton (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown (int keyCode, KeyEvent event)
|
||||
{
|
||||
if (host == 0)
|
||||
return false;
|
||||
|
||||
// The key event may move the cursor, or in some cases it might enter characters (e.g.
|
||||
// digits). In this case, we need to reset the IME so that it's aware of the new contents
|
||||
// of the TextInputTarget.
|
||||
closeInputMethodContext();
|
||||
|
||||
switch (keyCode)
|
||||
{
|
||||
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||
return super.onKeyDown (keyCode, event);
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
{
|
||||
backButtonPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
handleKeyDown (host,
|
||||
keyCode,
|
||||
event.getUnicodeChar(),
|
||||
event.getMetaState());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp (int keyCode, KeyEvent event)
|
||||
{
|
||||
if (host == 0)
|
||||
return false;
|
||||
|
||||
handleKeyUp (host, keyCode, event.getUnicodeChar());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)
|
||||
{
|
||||
if (host == 0)
|
||||
return false;
|
||||
|
||||
if (keyCode != KeyEvent.KEYCODE_UNKNOWN || (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && event.getAction() != KeyEvent.ACTION_MULTIPLE))
|
||||
return super.onKeyMultiple (keyCode, count, event);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && event.getCharacters() != null)
|
||||
{
|
||||
int utf8Char = event.getCharacters().codePointAt (0);
|
||||
|
||||
handleKeyDown (host,
|
||||
keyCode,
|
||||
utf8Char,
|
||||
event.getMetaState());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
private final class KeyboardDismissListener
|
||||
{
|
||||
public KeyboardDismissListener (ComponentPeerView viewToUse)
|
||||
{
|
||||
view = viewToUse;
|
||||
}
|
||||
|
||||
private void startListening()
|
||||
{
|
||||
view.getViewTreeObserver().addOnGlobalLayoutListener (viewTreeObserver);
|
||||
}
|
||||
|
||||
private void stopListening()
|
||||
{
|
||||
view.getViewTreeObserver().removeOnGlobalLayoutListener (viewTreeObserver);
|
||||
}
|
||||
|
||||
private class TreeObserver implements ViewTreeObserver.OnGlobalLayoutListener
|
||||
{
|
||||
TreeObserver()
|
||||
{
|
||||
keyboardShown = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGlobalLayout()
|
||||
{
|
||||
Rect r = new Rect();
|
||||
|
||||
View parentView = getRootView();
|
||||
int diff;
|
||||
|
||||
if (parentView == null)
|
||||
{
|
||||
getWindowVisibleDisplayFrame (r);
|
||||
diff = getHeight() - (r.bottom - r.top);
|
||||
}
|
||||
else
|
||||
{
|
||||
parentView.getWindowVisibleDisplayFrame (r);
|
||||
diff = parentView.getHeight() - (r.bottom - r.top);
|
||||
}
|
||||
|
||||
// Arbitrary threshold, surely keyboard would take more than 20 pix.
|
||||
if (diff < 20 && keyboardShown)
|
||||
{
|
||||
keyboardShown = false;
|
||||
handleKeyboardHidden (view.host);
|
||||
}
|
||||
|
||||
if (! keyboardShown && diff > 20)
|
||||
keyboardShown = true;
|
||||
}
|
||||
|
||||
private boolean keyboardShown;
|
||||
}
|
||||
|
||||
private final ComponentPeerView view;
|
||||
private final TreeObserver viewTreeObserver = new TreeObserver();
|
||||
}
|
||||
|
||||
private final KeyboardDismissListener keyboardDismissListener = new KeyboardDismissListener (this);
|
||||
|
||||
//==============================================================================
|
||||
// This implementation is quite similar to the ChangeListener in Android's built-in TextView.
|
||||
private static final class ChangeWatcher implements SpanWatcher, TextWatcher
|
||||
{
|
||||
public ChangeWatcher (ComponentPeerView viewIn, Editable editableIn, TextInputTarget targetIn)
|
||||
{
|
||||
view = viewIn;
|
||||
editable = editableIn;
|
||||
target = targetIn;
|
||||
|
||||
updateEditableSelectionFromTarget (editable, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpanAdded (Spannable text, Object what, int start, int end)
|
||||
{
|
||||
updateTargetRangesFromEditable (editable, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpanRemoved (Spannable text, Object what, int start, int end)
|
||||
{
|
||||
updateTargetRangesFromEditable (editable, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSpanChanged (Spannable text, Object what, int ostart, int oend, int nstart, int nend)
|
||||
{
|
||||
updateTargetRangesFromEditable (editable, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged (Editable s)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged (CharSequence s, int start, int count, int after)
|
||||
{
|
||||
contentsBeforeChange = s.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged (CharSequence s, int start, int before, int count)
|
||||
{
|
||||
if (editable != s || contentsBeforeChange == null)
|
||||
return;
|
||||
|
||||
final String newText = s.subSequence (start, start + count).toString();
|
||||
|
||||
int code = 0;
|
||||
|
||||
if (newText.endsWith ("\n") || newText.endsWith ("\r"))
|
||||
code = KeyEvent.KEYCODE_ENTER;
|
||||
|
||||
if (newText.endsWith ("\t"))
|
||||
code = KeyEvent.KEYCODE_TAB;
|
||||
|
||||
target.setHighlightedRegion (contentsBeforeChange.codePointCount (0, start),
|
||||
contentsBeforeChange.codePointCount (0, start + before));
|
||||
target.insertTextAtCaret (code != 0 ? newText.substring (0, newText.length() - 1)
|
||||
: newText);
|
||||
|
||||
// Treating return/tab as individual keypresses rather than part of the composition
|
||||
// sequence allows TextEditor onReturn and onTab to work as expected.
|
||||
if (code != 0)
|
||||
view.onKeyDown (code, new KeyEvent (KeyEvent.ACTION_DOWN, code));
|
||||
|
||||
updateTargetRangesFromEditable (editable, target);
|
||||
contentsBeforeChange = null;
|
||||
}
|
||||
|
||||
private static void updateEditableSelectionFromTarget (Editable editable, TextInputTarget text)
|
||||
{
|
||||
final int start = text.getHighlightedRegionBegin();
|
||||
final int end = text.getHighlightedRegionEnd();
|
||||
|
||||
if (start < 0 || end < 0)
|
||||
return;
|
||||
|
||||
final String string = editable.toString();
|
||||
Selection.setSelection (editable,
|
||||
string.offsetByCodePoints (0, start),
|
||||
string.offsetByCodePoints (0, end));
|
||||
}
|
||||
|
||||
private static void updateTargetSelectionFromEditable (Editable editable, TextInputTarget target)
|
||||
{
|
||||
final int start = Selection.getSelectionStart (editable);
|
||||
final int end = Selection.getSelectionEnd (editable);
|
||||
|
||||
if (start < 0 || end < 0)
|
||||
return;
|
||||
|
||||
final String string = editable.toString();
|
||||
target.setHighlightedRegion (string.codePointCount (0, start),
|
||||
string.codePointCount (0, end));
|
||||
}
|
||||
|
||||
private static List<Pair<Integer, Integer>> getUnderlinedRanges (Editable editable)
|
||||
{
|
||||
final int start = BaseInputConnection.getComposingSpanStart (editable);
|
||||
final int end = BaseInputConnection.getComposingSpanEnd (editable);
|
||||
|
||||
if (start < 0 || end < 0)
|
||||
return null;
|
||||
|
||||
final String string = editable.toString();
|
||||
|
||||
final ArrayList<Pair<Integer, Integer>> pairs = new ArrayList<>();
|
||||
pairs.add (new Pair<> (string.codePointCount (0, start), string.codePointCount (0, end)));
|
||||
return pairs;
|
||||
}
|
||||
|
||||
private static void updateTargetCompositionRangesFromEditable (Editable editable, TextInputTarget target)
|
||||
{
|
||||
target.setTemporaryUnderlining (getUnderlinedRanges (editable));
|
||||
}
|
||||
|
||||
private static void updateTargetRangesFromEditable (Editable editable, TextInputTarget target)
|
||||
{
|
||||
updateTargetSelectionFromEditable (editable, target);
|
||||
updateTargetCompositionRangesFromEditable (editable, target);
|
||||
}
|
||||
|
||||
private final ComponentPeerView view;
|
||||
private final TextInputTarget target;
|
||||
private final Editable editable;
|
||||
private String contentsBeforeChange;
|
||||
}
|
||||
|
||||
private static final class Connection extends BaseInputConnection
|
||||
{
|
||||
Connection (ComponentPeerView viewIn, boolean fullEditor, TextInputTarget targetIn)
|
||||
{
|
||||
super (viewIn, fullEditor);
|
||||
view = viewIn;
|
||||
target = targetIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Editable getEditable()
|
||||
{
|
||||
if (cached != null)
|
||||
return cached;
|
||||
|
||||
if (target == null)
|
||||
return cached = super.getEditable();
|
||||
|
||||
int length = target.getTotalNumChars();
|
||||
String initialText = target.getTextInRange (0, length);
|
||||
cached = new SpannableStringBuilder (initialText);
|
||||
// Span the entire range of text, so that we pick up changes at any location.
|
||||
// Use cached.length rather than target.getTotalNumChars here, because this
|
||||
// range is in UTF-16 code units, rather than code points.
|
||||
changeWatcher = new ChangeWatcher (view, cached, target);
|
||||
cached.setSpan (changeWatcher, 0, cached.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
return cached;
|
||||
}
|
||||
|
||||
/** Call this to stop listening for selection/composition updates.
|
||||
|
||||
We do this before closing the current input method context (e.g. when the user
|
||||
taps on a text view to move the cursor), because otherwise the input system
|
||||
might send another round of notifications *during* the restartInput call, after we've
|
||||
requested that the input session should end.
|
||||
*/
|
||||
@Override
|
||||
public void closeConnection()
|
||||
{
|
||||
if (cached != null && changeWatcher != null)
|
||||
cached.removeSpan (changeWatcher);
|
||||
|
||||
cached = null;
|
||||
target = null;
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
{
|
||||
super.closeConnection();
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
setImeConsumesInput (false);
|
||||
}
|
||||
else
|
||||
{
|
||||
finishComposingText();
|
||||
}
|
||||
}
|
||||
|
||||
private ComponentPeerView view;
|
||||
private TextInputTarget target;
|
||||
private Editable cached;
|
||||
private ChangeWatcher changeWatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection (EditorInfo outAttrs)
|
||||
{
|
||||
TextInputTarget focused = getFocusedTextInputTarget (host);
|
||||
|
||||
outAttrs.actionLabel = "";
|
||||
outAttrs.hintText = "";
|
||||
outAttrs.initialCapsMode = 0;
|
||||
outAttrs.initialSelStart = focused != null ? focused.getHighlightedRegionBegin() : -1;
|
||||
outAttrs.initialSelEnd = focused != null ? focused.getHighlightedRegionEnd() : -1;
|
||||
outAttrs.label = "";
|
||||
outAttrs.imeOptions = EditorInfo.IME_ACTION_UNSPECIFIED
|
||||
| EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
||||
| EditorInfo.IME_FLAG_NO_ENTER_ACTION;
|
||||
outAttrs.inputType = focused != null ? getInputTypeForJuceVirtualKeyboardType (focused.getKeyboardType())
|
||||
: 0;
|
||||
|
||||
cachedConnection = new Connection (this, true, focused);
|
||||
return cachedConnection;
|
||||
}
|
||||
|
||||
private Connection cachedConnection;
|
||||
|
||||
//==============================================================================
|
||||
@Override
|
||||
protected void onSizeChanged (int w, int h, int oldw, int oldh)
|
||||
{
|
||||
super.onSizeChanged (w, h, oldw, oldh);
|
||||
|
||||
if (host != 0)
|
||||
viewSizeChanged (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout (boolean changed, int left, int top, int right, int bottom)
|
||||
{
|
||||
}
|
||||
|
||||
private native void viewSizeChanged (long host);
|
||||
|
||||
@Override
|
||||
public void onFocusChange (View v, boolean hasFocus)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
if (v == this)
|
||||
focusChanged (host, hasFocus);
|
||||
}
|
||||
|
||||
private native void focusChanged (long host, boolean hasFocus);
|
||||
|
||||
public void setViewName (String newName)
|
||||
{
|
||||
}
|
||||
|
||||
public void setSystemUiVisibilityCompat (int visibility)
|
||||
{
|
||||
Method systemUIVisibilityMethod = null;
|
||||
try
|
||||
{
|
||||
systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class);
|
||||
}
|
||||
catch (SecurityException e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (NoSuchMethodException e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (systemUIVisibilityMethod == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
systemUIVisibilityMethod.invoke (this, visibility);
|
||||
}
|
||||
catch (java.lang.IllegalArgumentException e)
|
||||
{
|
||||
}
|
||||
catch (java.lang.IllegalAccessException e)
|
||||
{
|
||||
}
|
||||
catch (java.lang.reflect.InvocationTargetException e)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisible()
|
||||
{
|
||||
return getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
public void setVisible (boolean b)
|
||||
{
|
||||
setVisibility (b ? VISIBLE : INVISIBLE);
|
||||
}
|
||||
|
||||
public boolean containsPoint (int x, int y)
|
||||
{
|
||||
return true; //xxx needs to check overlapping views
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
private native void handleAppPaused (long host);
|
||||
private native void handleAppResumed (long host);
|
||||
|
||||
@Override
|
||||
public void onActivityPaused (Activity activity)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
handleAppPaused (host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStopped (Activity activity)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivitySaveInstanceState (Activity activity, Bundle bundle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroyed (Activity activity)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated (Activity activity, Bundle bundle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityStarted (Activity activity)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResumed (Activity activity)
|
||||
{
|
||||
if (host == 0)
|
||||
return;
|
||||
|
||||
// Ensure that navigation/status bar visibility is correctly restored.
|
||||
handleAppResumed (host);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
private native View getNativeView (long host, int virtualViewId);
|
||||
private native boolean populateAccessibilityNodeInfo (long host, int virtualViewId, AccessibilityNodeInfo info);
|
||||
private native boolean handlePerformAction (long host, int virtualViewId, int action, Bundle arguments);
|
||||
private native Integer getInputFocusViewId (long host);
|
||||
private native Integer getAccessibilityFocusViewId (long host);
|
||||
|
||||
private final class JuceAccessibilityNodeProvider extends AccessibilityNodeProvider
|
||||
{
|
||||
public JuceAccessibilityNodeProvider (ComponentPeerView viewToUse)
|
||||
{
|
||||
view = viewToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessibilityNodeInfo createAccessibilityNodeInfo (int virtualViewId)
|
||||
{
|
||||
if (host == 0)
|
||||
return null;
|
||||
|
||||
View nativeView = getNativeView (host, virtualViewId);
|
||||
|
||||
if (nativeView != null)
|
||||
return nativeView.createAccessibilityNodeInfo();
|
||||
|
||||
final AccessibilityNodeInfo nodeInfo;
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
|
||||
{
|
||||
nodeInfo = new AccessibilityNodeInfo (view, virtualViewId);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeInfo = AccessibilityNodeInfo.obtain (view, virtualViewId);
|
||||
}
|
||||
|
||||
if (! populateAccessibilityNodeInfo (host, virtualViewId, nodeInfo))
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R)
|
||||
nodeInfo.recycle();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText (String text, int virtualViewId)
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessibilityNodeInfo findFocus (int focus)
|
||||
{
|
||||
if (host == 0)
|
||||
return null;
|
||||
|
||||
Integer focusViewId = (focus == AccessibilityNodeInfo.FOCUS_INPUT ? getInputFocusViewId (host)
|
||||
: getAccessibilityFocusViewId (host));
|
||||
|
||||
if (focusViewId != null)
|
||||
return createAccessibilityNodeInfo (focusViewId);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAction (int virtualViewId, int action, Bundle arguments)
|
||||
{
|
||||
if (host == 0)
|
||||
return false;
|
||||
|
||||
return handlePerformAction (host, virtualViewId, action, arguments);
|
||||
}
|
||||
|
||||
private final ComponentPeerView view;
|
||||
}
|
||||
|
||||
private final JuceAccessibilityNodeProvider nodeProvider = new JuceAccessibilityNodeProvider (this);
|
||||
private final AccessibilityManager accessibilityManager = (AccessibilityManager) getContext().getSystemService (Context.ACCESSIBILITY_SERVICE);
|
||||
|
||||
@Override
|
||||
public AccessibilityNodeProvider getAccessibilityNodeProvider()
|
||||
{
|
||||
return nodeProvider;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
|
||||
import java.lang.String;
|
||||
|
||||
public final class JuceContentProviderCursor extends MatrixCursor
|
||||
{
|
||||
public JuceContentProviderCursor (long hostToUse, String[] columnNames)
|
||||
{
|
||||
super (columnNames);
|
||||
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close ()
|
||||
{
|
||||
super.close ();
|
||||
|
||||
contentSharerCursorClosed (host);
|
||||
}
|
||||
|
||||
private native void contentSharerCursorClosed (long host);
|
||||
|
||||
private long host;
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.os.FileObserver;
|
||||
|
||||
import java.lang.String;
|
||||
|
||||
public final class JuceContentProviderFileObserver extends FileObserver
|
||||
{
|
||||
public JuceContentProviderFileObserver (long hostToUse, String path, int mask)
|
||||
{
|
||||
super (path, mask);
|
||||
host = hostToUse;
|
||||
}
|
||||
|
||||
public void onEvent (int event, String path)
|
||||
{
|
||||
contentSharerFileObserverEvent (host, event, path);
|
||||
}
|
||||
|
||||
private long host;
|
||||
|
||||
private native void contentSharerFileObserverEvent (long host, int event, String path);
|
||||
}
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
|
||||
//==============================================================================
|
||||
public class JuceActivity extends Activity
|
||||
{
|
||||
//==============================================================================
|
||||
private native void appNewIntent (Intent intent);
|
||||
private native void appOnResume();
|
||||
|
||||
@Override
|
||||
protected void onNewIntent (Intent intent)
|
||||
{
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
|
||||
appNewIntent (intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume()
|
||||
{
|
||||
super.onResume();
|
||||
|
||||
appOnResume();
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.net.Uri;
|
||||
import android.os.FileObserver;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.lang.String;
|
||||
|
||||
public final class JuceSharingContentProvider extends ContentProvider
|
||||
{
|
||||
private Object lock = new Object ();
|
||||
|
||||
private native Cursor contentSharerQuery (Uri uri, String[] projection);
|
||||
private native AssetFileDescriptor contentSharerOpenFile (Uri uri, String mode);
|
||||
private native String[] contentSharerGetStreamTypes (Uri uri, String mimeTypeFilter);
|
||||
|
||||
@Override
|
||||
public boolean onCreate ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query (Uri url, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder)
|
||||
{
|
||||
synchronized (lock)
|
||||
{
|
||||
return contentSharerQuery (url, projection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert (Uri uri, ContentValues values)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update (Uri uri, ContentValues values, String selection,
|
||||
String[] selectionArgs)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete (Uri uri, String selection, String[] selectionArgs)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType (Uri uri)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openAssetFile (Uri uri, String mode)
|
||||
{
|
||||
synchronized (lock)
|
||||
{
|
||||
return contentSharerOpenFile (uri, mode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile (Uri uri, String mode)
|
||||
{
|
||||
synchronized (lock)
|
||||
{
|
||||
AssetFileDescriptor result = contentSharerOpenFile (uri, mode);
|
||||
|
||||
if (result != null)
|
||||
return result.getParcelFileDescriptor ();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getStreamTypes (Uri uri, String mimeTypeFilter)
|
||||
{
|
||||
synchronized (lock)
|
||||
{
|
||||
return contentSharerGetStreamTypes (uri, mimeTypeFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
package com.rmsl.juce;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
//==============================================================================
|
||||
public class Receiver extends BroadcastReceiver
|
||||
{
|
||||
@Override
|
||||
public void onReceive (Context context, Intent intent)
|
||||
{
|
||||
onBroadcastResultNative (intent.getIntExtra ("com.rmsl.juce.JUCE_REQUEST_CODE", 0));
|
||||
}
|
||||
|
||||
private native void onBroadcastResultNative (int requestCode);
|
||||
}
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
// The CoreGraphicsMetalLayerRenderer requires macOS 10.14 and iOS 12.
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new")
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class CoreGraphicsMetalLayerRenderer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
static auto create()
|
||||
{
|
||||
ObjCObjectHandle<id<MTLDevice>> device { MTLCreateSystemDefaultDevice() };
|
||||
return rawToUniquePtr (device != nullptr ? new CoreGraphicsMetalLayerRenderer (device)
|
||||
: nullptr);
|
||||
}
|
||||
|
||||
~CoreGraphicsMetalLayerRenderer()
|
||||
{
|
||||
if (memoryBlitCommandBuffer != nullptr)
|
||||
{
|
||||
stopGpuCommandSubmission = true;
|
||||
[memoryBlitCommandBuffer.get() waitUntilCompleted];
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns any regions that weren't redrawn, and which should be retried next frame. */
|
||||
template <typename Callback>
|
||||
[[nodiscard]] RectangleList<float> drawRectangleList (CAMetalLayer* layer,
|
||||
float scaleFactor,
|
||||
Callback&& drawRectWithContext,
|
||||
RectangleList<float> dirtyRegions,
|
||||
const bool renderSync)
|
||||
{
|
||||
layer.presentsWithTransaction = renderSync;
|
||||
|
||||
if (memoryBlitCommandBuffer != nullptr)
|
||||
{
|
||||
switch ([memoryBlitCommandBuffer.get() status])
|
||||
{
|
||||
case MTLCommandBufferStatusNotEnqueued:
|
||||
case MTLCommandBufferStatusEnqueued:
|
||||
case MTLCommandBufferStatusCommitted:
|
||||
case MTLCommandBufferStatusScheduled:
|
||||
// If we haven't finished blitting the CPU texture to the GPU then
|
||||
// report that we have been unable to draw anything.
|
||||
return dirtyRegions;
|
||||
case MTLCommandBufferStatusCompleted:
|
||||
case MTLCommandBufferStatusError:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
layer.contentsScale = scaleFactor;
|
||||
|
||||
const auto drawableSizeTransform = CGAffineTransformMakeScale (layer.contentsScale, layer.contentsScale);
|
||||
const auto transformedFrameSize = CGSizeApplyAffineTransform (layer.bounds.size, drawableSizeTransform);
|
||||
|
||||
if (CGSizeEqualToSize (transformedFrameSize, CGSizeZero))
|
||||
return dirtyRegions;
|
||||
|
||||
if (resources == nullptr || ! CGSizeEqualToSize (layer.drawableSize, transformedFrameSize))
|
||||
{
|
||||
layer.drawableSize = transformedFrameSize;
|
||||
resources = std::make_unique<Resources> (device.get(), layer);
|
||||
dirtyRegions.clear();
|
||||
dirtyRegions.add (convertToRectFloat (layer.bounds));
|
||||
}
|
||||
|
||||
auto gpuTexture = resources->getGpuTexture();
|
||||
|
||||
if (gpuTexture == nullptr)
|
||||
{
|
||||
jassertfalse;
|
||||
return dirtyRegions;
|
||||
}
|
||||
|
||||
auto cgContext = resources->getCGContext();
|
||||
|
||||
for (auto rect : dirtyRegions)
|
||||
{
|
||||
const auto cgRect = convertToCGRect (rect);
|
||||
|
||||
CGContextSaveGState (cgContext);
|
||||
|
||||
CGContextClipToRect (cgContext, cgRect);
|
||||
drawRectWithContext (cgContext, cgRect);
|
||||
|
||||
CGContextRestoreGState (cgContext);
|
||||
}
|
||||
|
||||
resources->signalBufferModifiedByCpu();
|
||||
|
||||
auto sharedTexture = resources->getSharedTexture();
|
||||
|
||||
auto encodeBlit = [] (id<MTLCommandBuffer> commandBuffer,
|
||||
id<MTLTexture> source,
|
||||
id<MTLTexture> destination)
|
||||
{
|
||||
auto blitCommandEncoder = [commandBuffer blitCommandEncoder];
|
||||
[blitCommandEncoder copyFromTexture: source
|
||||
sourceSlice: 0
|
||||
sourceLevel: 0
|
||||
sourceOrigin: MTLOrigin{}
|
||||
sourceSize: MTLSize { source.width, source.height, 1 }
|
||||
toTexture: destination
|
||||
destinationSlice: 0
|
||||
destinationLevel: 0
|
||||
destinationOrigin: MTLOrigin{}];
|
||||
[blitCommandEncoder endEncoding];
|
||||
};
|
||||
|
||||
if (renderSync)
|
||||
{
|
||||
@autoreleasepool
|
||||
{
|
||||
id<MTLCommandBuffer> commandBuffer = [commandQueue.get() commandBuffer];
|
||||
|
||||
id<CAMetalDrawable> drawable = [layer nextDrawable];
|
||||
encodeBlit (commandBuffer, sharedTexture, drawable.texture);
|
||||
|
||||
[commandBuffer commit];
|
||||
[commandBuffer waitUntilScheduled];
|
||||
[drawable present];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Command buffers are usually considered temporary, and are automatically released by
|
||||
// the operating system when the rendering pipeline is finished. However, we want to keep
|
||||
// this one alive so that we can wait for pipeline completion in the destructor.
|
||||
memoryBlitCommandBuffer.reset ([[commandQueue.get() commandBuffer] retain]);
|
||||
|
||||
encodeBlit (memoryBlitCommandBuffer.get(), sharedTexture, gpuTexture);
|
||||
|
||||
[memoryBlitCommandBuffer.get() addScheduledHandler: ^(id<MTLCommandBuffer>)
|
||||
{
|
||||
// We're on a Metal thread, so we can make a blocking nextDrawable call
|
||||
// without stalling the message thread.
|
||||
|
||||
// Check if we can do an early exit.
|
||||
if (stopGpuCommandSubmission)
|
||||
return;
|
||||
|
||||
@autoreleasepool
|
||||
{
|
||||
id<CAMetalDrawable> drawable = [layer nextDrawable];
|
||||
|
||||
id<MTLCommandBuffer> presentationCommandBuffer = [commandQueue.get() commandBuffer];
|
||||
|
||||
encodeBlit (presentationCommandBuffer, gpuTexture, drawable.texture);
|
||||
|
||||
[presentationCommandBuffer addScheduledHandler: ^(id<MTLCommandBuffer>)
|
||||
{
|
||||
[drawable present];
|
||||
}];
|
||||
|
||||
[presentationCommandBuffer commit];
|
||||
}
|
||||
}];
|
||||
|
||||
[memoryBlitCommandBuffer.get() commit];
|
||||
}
|
||||
|
||||
dirtyRegions.clear();
|
||||
return dirtyRegions;
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
explicit CoreGraphicsMetalLayerRenderer (ObjCObjectHandle<id<MTLDevice>> mtlDevice)
|
||||
: device (mtlDevice),
|
||||
commandQueue ([device.get() newCommandQueue])
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static auto alignTo (size_t n, size_t alignment)
|
||||
{
|
||||
return ((n + alignment - 1) / alignment) * alignment;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class GpuTexturePool
|
||||
{
|
||||
public:
|
||||
GpuTexturePool (id<MTLDevice> metalDevice, MTLTextureDescriptor* descriptor)
|
||||
{
|
||||
for (auto& t : textureCache)
|
||||
t.reset ((descriptor.width != 0 && descriptor.height != 0) ? [metalDevice newTextureWithDescriptor: descriptor]
|
||||
: nullptr);
|
||||
}
|
||||
|
||||
id<MTLTexture> take() const
|
||||
{
|
||||
auto iter = std::find_if (textureCache.begin(), textureCache.end(),
|
||||
[] (const ObjCObjectHandle<id<MTLTexture>>& t) { return [t.get() retainCount] == 1; });
|
||||
return iter == textureCache.end() ? nullptr : (*iter).get();
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<ObjCObjectHandle<id<MTLTexture>>, 3> textureCache;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GpuTexturePool)
|
||||
JUCE_DECLARE_NON_MOVEABLE (GpuTexturePool)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class Resources
|
||||
{
|
||||
public:
|
||||
Resources (id<MTLDevice> metalDevice, CAMetalLayer* layer)
|
||||
{
|
||||
const auto bytesPerRow = alignTo ((size_t) layer.drawableSize.width * 4, 256);
|
||||
|
||||
const auto allocationSize = cpuRenderMemory.ensureSize (bytesPerRow * (size_t) layer.drawableSize.height);
|
||||
|
||||
buffer.reset ([metalDevice newBufferWithBytesNoCopy: cpuRenderMemory.get()
|
||||
length: allocationSize
|
||||
options:
|
||||
#if JUCE_MAC
|
||||
MTLResourceStorageModeManaged
|
||||
#else
|
||||
MTLResourceStorageModeShared
|
||||
#endif
|
||||
deallocator: nullptr]);
|
||||
|
||||
auto* textureDesc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat: layer.pixelFormat
|
||||
width: (NSUInteger) layer.drawableSize.width
|
||||
height: (NSUInteger) layer.drawableSize.height
|
||||
mipmapped: NO];
|
||||
textureDesc.storageMode =
|
||||
#if JUCE_MAC
|
||||
MTLStorageModeManaged;
|
||||
#else
|
||||
MTLStorageModeShared;
|
||||
#endif
|
||||
textureDesc.usage = MTLTextureUsageShaderRead;
|
||||
|
||||
sharedTexture.reset ([buffer.get() newTextureWithDescriptor: textureDesc
|
||||
offset: 0
|
||||
bytesPerRow: bytesPerRow]);
|
||||
|
||||
cgContext.reset (CGBitmapContextCreate (cpuRenderMemory.get(),
|
||||
(size_t) layer.drawableSize.width,
|
||||
(size_t) layer.drawableSize.height,
|
||||
8, // Bits per component
|
||||
bytesPerRow,
|
||||
CGColorSpaceCreateWithName (kCGColorSpaceSRGB),
|
||||
(uint32_t) kCGImageAlphaPremultipliedFirst | (uint32_t) kCGBitmapByteOrder32Host));
|
||||
|
||||
CGContextTranslateCTM (cgContext.get(), 0, layer.drawableSize.height);
|
||||
CGContextScaleCTM (cgContext.get(), layer.contentsScale, -layer.contentsScale);
|
||||
|
||||
textureDesc.storageMode = MTLStorageModePrivate;
|
||||
gpuTexturePool = std::make_unique<GpuTexturePool> (metalDevice, textureDesc);
|
||||
}
|
||||
|
||||
CGContextRef getCGContext() const noexcept { return cgContext.get(); }
|
||||
id<MTLTexture> getSharedTexture() const noexcept { return sharedTexture.get(); }
|
||||
id<MTLTexture> getGpuTexture() noexcept { return gpuTexturePool == nullptr ? nullptr : gpuTexturePool->take(); }
|
||||
|
||||
void signalBufferModifiedByCpu()
|
||||
{
|
||||
#if JUCE_MAC
|
||||
[buffer.get() didModifyRange: { 0, buffer.get().length }];
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
class AlignedMemory
|
||||
{
|
||||
public:
|
||||
AlignedMemory() = default;
|
||||
|
||||
void* get()
|
||||
{
|
||||
return allocation != nullptr ? allocation->data : nullptr;
|
||||
}
|
||||
|
||||
size_t ensureSize (size_t newSize)
|
||||
{
|
||||
const auto alignedSize = alignTo (newSize, pagesize);
|
||||
|
||||
if (alignedSize > size)
|
||||
{
|
||||
size = std::max (alignedSize, alignTo ((size_t) ((float) size * growthFactor), pagesize));
|
||||
allocation = std::make_unique<AllocationWrapper> (pagesize, size);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr float growthFactor = 1.3f;
|
||||
|
||||
const size_t pagesize = (size_t) getpagesize();
|
||||
|
||||
struct AllocationWrapper
|
||||
{
|
||||
AllocationWrapper (size_t alignment, size_t allocationSize)
|
||||
{
|
||||
if (posix_memalign (&data, alignment, allocationSize) != 0)
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
~AllocationWrapper()
|
||||
{
|
||||
::free (data);
|
||||
}
|
||||
|
||||
void* data = nullptr;
|
||||
};
|
||||
|
||||
std::unique_ptr<AllocationWrapper> allocation;
|
||||
size_t size = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlignedMemory)
|
||||
JUCE_DECLARE_NON_MOVEABLE (AlignedMemory)
|
||||
};
|
||||
|
||||
AlignedMemory cpuRenderMemory;
|
||||
|
||||
detail::ContextPtr cgContext;
|
||||
|
||||
ObjCObjectHandle<id<MTLBuffer>> buffer;
|
||||
ObjCObjectHandle<id<MTLTexture>> sharedTexture;
|
||||
std::unique_ptr<GpuTexturePool> gpuTexturePool;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Resources)
|
||||
JUCE_DECLARE_NON_MOVEABLE (Resources)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<Resources> resources;
|
||||
|
||||
ObjCObjectHandle<id<MTLDevice>> device;
|
||||
ObjCObjectHandle<id<MTLCommandQueue>> commandQueue;
|
||||
ObjCObjectHandle<id<MTLCommandBuffer>> memoryBlitCommandBuffer;
|
||||
|
||||
std::atomic<bool> stopGpuCommandSubmission { false };
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreGraphicsMetalLayerRenderer)
|
||||
JUCE_DECLARE_NON_MOVEABLE (CoreGraphicsMetalLayerRenderer)
|
||||
};
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
} // namespace juce
|
||||
+957
@@ -0,0 +1,957 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
// This byte-code is generated from native/java/app/com/rmsl/juce/JuceContentProviderCursor.java with min sdk version 16
|
||||
// See juce_core/native/java/README.txt on how to generate this byte-code.
|
||||
static const uint8 javaJuceContentProviderCursor[] =
|
||||
{31,139,8,8,191,114,161,94,0,3,106,97,118,97,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,67,117,
|
||||
114,115,111,114,46,100,101,120,0,117,147,177,111,211,64,20,198,223,157,157,148,150,54,164,192,208,14,64,144,16,18,67,235,138,2,
|
||||
75,40,162,10,44,150,65,149,2,25,218,233,176,173,198,37,241,69,182,19,121,96,160,21,136,37,19,98,234,80,85,149,152,88,24,248,
|
||||
3,24,146,63,130,141,137,129,13,169,99,7,190,203,157,33,18,194,210,207,247,222,229,189,239,157,206,95,130,48,159,91,91,191,75,227,
|
||||
60,200,143,134,239,247,151,62,189,43,175,127,249,246,235,241,215,241,112,231,231,193,237,135,22,81,143,136,242,214,157,139,
|
||||
100,158,99,78,84,37,189,95,2,159,129,13,70,128,129,83,179,127,102,242,27,120,157,129,71,224,16,156,128,143,96,12,126,128,69,232,
|
||||
93,6,75,224,10,184,14,238,129,13,224,130,16,188,4,3,174,245,44,51,79,205,152,53,171,101,206,86,54,241,27,20,206,152,120,136,
|
||||
248,156,137,63,32,134,12,45,76,206,166,187,148,230,28,169,125,62,201,249,159,156,209,188,201,23,77,93,241,187,122,134,38,40,225,
|
||||
52,42,124,197,245,252,94,141,104,147,182,113,95,21,76,208,83,222,114,125,86,89,101,168,109,162,162,183,134,46,86,249,71,215,
|
||||
158,228,54,149,239,71,113,148,61,32,230,210,85,183,239,135,13,25,103,97,156,109,37,114,16,5,97,210,232,39,169,76,86,247,196,64,
|
||||
208,53,79,196,65,34,163,192,9,68,38,94,136,52,116,158,136,44,137,114,93,84,167,91,158,47,187,78,210,77,59,206,30,164,156,255,
|
||||
234,213,137,181,136,183,92,178,90,174,135,192,163,75,59,158,154,225,116,68,188,235,52,33,26,239,214,169,228,119,100,26,210,121,
|
||||
95,118,250,221,248,169,232,134,41,45,251,90,176,217,22,73,33,215,80,101,1,217,109,153,102,52,171,222,207,228,115,52,218,89,
|
||||
59,74,233,38,191,48,63,83,217,88,161,85,194,178,141,139,224,184,28,190,255,218,30,113,126,192,201,98,223,249,130,185,27,54,181,
|
||||
22,222,227,83,254,43,60,49,50,235,180,15,11,47,150,167,252,200,106,186,95,121,146,85,255,122,134,215,180,190,242,169,101,106,
|
||||
212,119,165,154,238,157,124,243,170,142,213,255,224,55,143,234,50,200,64,3,0,0,0,0};
|
||||
|
||||
// This byte-code is generated from native/java/app/com/rmsl/juce/JuceContentProviderFileObserver.java with min sdk version 16
|
||||
// See juce_core/native/java/README.txt on how to generate this byte-code.
|
||||
static const uint8 javaJuceContentProviderFileObserver[] =
|
||||
{31,139,8,8,194,122,161,94,0,3,106,97,118,97,74,117,99,101,67,111,110,116,101,110,116,80,114,111,118,105,100,101,114,70,105,
|
||||
108,101,79,98,115,101,114,118,101,114,46,100,101,120,0,133,147,205,107,19,65,24,198,223,249,72,98,171,46,105,235,69,16,201,65,81,
|
||||
68,221,136,10,66,84,144,250,65,194,130,197,212,32,5,15,155,100,104,182,38,187,97,119,141,241,32,126,30,196,147,23,79,246,216,
|
||||
131,120,202,77,169,80,212,191,64,193,66,143,30,60,138,255,130,62,179,51,165,219,147,129,223,188,239,188,239,204,179,179,179,79,
|
||||
186,106,60,93,61,123,158,54,159,255,248,112,97,210,120,124,98,237,251,177,7,109,245,115,253,225,198,159,47,243,171,135,198,130,
|
||||
104,72,68,227,214,185,89,178,191,45,78,116,128,76,189,8,62,3,169,235,128,129,61,204,204,203,204,204,171,24,142,99,207,2,226,
|
||||
4,124,4,159,192,6,248,5,254,130,42,250,87,193,13,224,129,91,224,14,184,11,30,129,23,224,21,120,3,222,130,53,240,158,27,125,110,
|
||||
159,95,176,231,41,233,51,216,249,75,44,152,178,249,107,228,211,54,95,69,190,215,230,239,144,11,40,57,153,150,200,222,81,100,
|
||||
170,166,190,47,139,68,51,185,200,237,93,8,27,191,218,66,17,138,186,54,225,230,44,195,42,209,149,194,18,238,206,201,58,250,121,
|
||||
235,182,215,172,160,191,200,137,159,113,172,158,204,246,50,251,62,38,151,89,103,251,29,139,23,131,48,72,47,19,171,19,107,208,
|
||||
145,198,253,142,154,143,194,84,133,233,66,28,141,130,174,138,175,7,125,117,179,157,168,120,164,226,211,43,254,200,167,131,158,
|
||||
31,118,227,40,232,186,81,226,230,219,53,114,189,78,52,112,227,65,210,119,87,32,229,254,71,175,70,179,158,150,116,251,126,184,
|
||||
236,54,211,56,8,151,107,196,90,36,90,117,143,100,171,97,70,175,142,2,134,195,29,35,213,236,249,241,110,161,107,35,148,169,160,
|
||||
178,32,123,81,146,210,148,30,23,163,219,137,34,57,240,147,123,84,138,66,179,76,14,253,180,71,50,237,5,9,29,21,229,185,153,146,
|
||||
115,233,20,157,228,206,92,201,89,194,21,113,70,156,61,125,34,191,113,246,12,223,143,253,198,101,237,183,223,133,229,226,182,103,
|
||||
121,206,183,34,231,93,153,243,111,129,118,60,92,164,29,31,179,138,217,175,189,204,202,102,141,246,24,175,24,125,237,111,97,
|
||||
215,104,15,80,197,236,205,252,81,54,185,254,255,252,3,243,31,208,130,120,3,0,0,0,0};
|
||||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
FIELD (authority, "authority", "Ljava/lang/String;")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidProviderInfo, "android/content/pm/ProviderInfo")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "(Landroid/os/ParcelFileDescriptor;JJ)V") \
|
||||
METHOD (createInputStream, "createInputStream", "()Ljava/io/FileInputStream;") \
|
||||
METHOD (getLength, "getLength", "()J")
|
||||
|
||||
DECLARE_JNI_CLASS (AssetFileDescriptor, "android/content/res/AssetFileDescriptor")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (close, "close", "()V")
|
||||
|
||||
DECLARE_JNI_CLASS (JavaCloseable, "java/io/Closeable")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
STATICMETHOD (open, "open", "(Ljava/io/File;I)Landroid/os/ParcelFileDescriptor;")
|
||||
|
||||
DECLARE_JNI_CLASS (ParcelFileDescriptor, "android/os/ParcelFileDescriptor")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
//==============================================================================
|
||||
class AndroidContentSharerCursor
|
||||
{
|
||||
public:
|
||||
AndroidContentSharerCursor (JNIEnv* env,
|
||||
const LocalRef<jobject>& contentProvider,
|
||||
const LocalRef<jobjectArray>& resultColumns,
|
||||
std::function<void (AndroidContentSharerCursor&)> onCloseIn)
|
||||
: onClose (std::move (onCloseIn)),
|
||||
cursor (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderCursor,
|
||||
JuceContentProviderCursor.constructor,
|
||||
reinterpret_cast<jlong> (this),
|
||||
resultColumns.get()))))
|
||||
{
|
||||
// the content provider must be created first
|
||||
jassert (contentProvider.get() != nullptr);
|
||||
}
|
||||
|
||||
jobject getNativeCursor() const { return cursor.get(); }
|
||||
|
||||
void addRow (LocalRef<jobjectArray>& values)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
env->CallVoidMethod (cursor.get(), JuceContentProviderCursor.addRow, values.get());
|
||||
}
|
||||
|
||||
private:
|
||||
static void cursorClosed (JNIEnv*, AndroidContentSharerCursor& t)
|
||||
{
|
||||
MessageManager::callAsync ([&t]
|
||||
{
|
||||
NullCheckedInvocation::invoke (t.onClose, t);
|
||||
});
|
||||
}
|
||||
|
||||
std::function<void (AndroidContentSharerCursor&)> onClose;
|
||||
GlobalRef cursor;
|
||||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (addRow, "addRow", "([Ljava/lang/Object;)V") \
|
||||
METHOD (constructor, "<init>", "(J[Ljava/lang/String;)V") \
|
||||
CALLBACK (generatedCallback<&AndroidContentSharerCursor::cursorClosed>, "contentSharerCursorClosed", "(J)V") \
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderCursor, "com/rmsl/juce/JuceContentProviderCursor", 16, javaJuceContentProviderCursor)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AndroidContentSharerFileObserver
|
||||
{
|
||||
public:
|
||||
AndroidContentSharerFileObserver (JNIEnv* env,
|
||||
const LocalRef<jobject>& contentProvider,
|
||||
const File& filepathToUse,
|
||||
std::function<void()> onCloseIn)
|
||||
: onClose (std::move (onCloseIn)),
|
||||
filepath (filepathToUse),
|
||||
fileObserver (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderFileObserver,
|
||||
JuceContentProviderFileObserver.constructor,
|
||||
reinterpret_cast<jlong> (this),
|
||||
javaString (filepath.getFullPathName()).get(),
|
||||
open | access | closeWrite | closeNoWrite))))
|
||||
{
|
||||
// the content provider must be created first
|
||||
jassert (contentProvider.get() != nullptr);
|
||||
|
||||
env->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.startWatching);
|
||||
}
|
||||
|
||||
void onFileEvent (int event, const LocalRef<jstring>&)
|
||||
{
|
||||
if (event == open)
|
||||
{
|
||||
++numOpenedHandles;
|
||||
}
|
||||
else if (event == access)
|
||||
{
|
||||
fileWasRead = true;
|
||||
}
|
||||
else if (event == closeNoWrite || event == closeWrite)
|
||||
{
|
||||
--numOpenedHandles;
|
||||
|
||||
// numOpenedHandles may get negative if we don't receive open handle event.
|
||||
if (fileWasRead && numOpenedHandles <= 0)
|
||||
{
|
||||
MessageManager::callAsync ([fileObserver = fileObserver, onClose = onClose]
|
||||
{
|
||||
getEnv()->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.stopWatching);
|
||||
NullCheckedInvocation::invoke (onClose);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int open = 32;
|
||||
static constexpr int access = 1;
|
||||
static constexpr int closeWrite = 8;
|
||||
static constexpr int closeNoWrite = 16;
|
||||
|
||||
std::function<void()> onClose;
|
||||
bool fileWasRead = false;
|
||||
int numOpenedHandles = 0;
|
||||
|
||||
File filepath;
|
||||
GlobalRef fileObserver;
|
||||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "(JLjava/lang/String;I)V") \
|
||||
METHOD (startWatching, "startWatching", "()V") \
|
||||
METHOD (stopWatching, "stopWatching", "()V") \
|
||||
CALLBACK (generatedCallback<&AndroidContentSharerFileObserver::onFileEventCallback>, "contentSharerFileObserverEvent", "(JILjava/lang/String;)V") \
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderFileObserver, "com/rmsl/juce/JuceContentProviderFileObserver", 16, javaJuceContentProviderFileObserver)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
static void onFileEventCallback (JNIEnv*, AndroidContentSharerFileObserver& t, jint event, jstring path)
|
||||
{
|
||||
t.onFileEvent (event, LocalRef<jstring> (path));
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ContentSharerGlobalImpl
|
||||
{
|
||||
public:
|
||||
static ContentSharerGlobalImpl& getInstance()
|
||||
{
|
||||
static ContentSharerGlobalImpl result;
|
||||
return result;
|
||||
}
|
||||
|
||||
const String packageName = juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (getAppContext().get(),
|
||||
AndroidContext.getPackageName)));
|
||||
const String uriBase = "content://" + packageName + ".sharingcontentprovider/";
|
||||
|
||||
std::unique_ptr<ActivityLauncher> sharePreparedFiles (const std::map<String, File>& fileForUriIn,
|
||||
const StringArray& mimeTypes,
|
||||
std::function<void (bool)> callback)
|
||||
{
|
||||
// This function should be called from the main thread, but must not race with singleton
|
||||
// access from other threads.
|
||||
const ScopedLock lock { mutex };
|
||||
|
||||
if (! isContentSharingEnabled())
|
||||
{
|
||||
// You need to enable "Content Sharing" in Projucer's Android exporter.
|
||||
jassertfalse;
|
||||
NullCheckedInvocation::invoke (callback, false);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
fileForUri.insert (fileForUriIn.begin(), fileForUriIn.end());
|
||||
|
||||
LocalRef<jobject> fileUris (env->NewObject (JavaArrayList, JavaArrayList.constructor, fileForUriIn.size()));
|
||||
|
||||
for (const auto& pair : fileForUriIn)
|
||||
{
|
||||
env->CallBooleanMethod (fileUris,
|
||||
JavaArrayList.add,
|
||||
env->CallStaticObjectMethod (AndroidUri,
|
||||
AndroidUri.parse,
|
||||
javaString (pair.first).get()));
|
||||
}
|
||||
|
||||
LocalRef<jobject> intent (env->NewObject (AndroidIntent, AndroidIntent.constructor));
|
||||
env->CallObjectMethod (intent,
|
||||
AndroidIntent.setAction,
|
||||
javaString ("android.intent.action.SEND_MULTIPLE").get());
|
||||
|
||||
env->CallObjectMethod (intent,
|
||||
AndroidIntent.setType,
|
||||
javaString (getCommonMimeType (mimeTypes)).get());
|
||||
|
||||
const auto permissions = [&]
|
||||
{
|
||||
constexpr int grantReadUriPermission = 1;
|
||||
constexpr int grantPrefixUriPermission = 128;
|
||||
|
||||
if (getAndroidSDKVersion() < 21)
|
||||
return grantReadUriPermission;
|
||||
|
||||
return grantReadUriPermission | grantPrefixUriPermission;
|
||||
};
|
||||
|
||||
env->CallObjectMethod (intent, AndroidIntent.setFlags, permissions);
|
||||
|
||||
env->CallObjectMethod (intent,
|
||||
AndroidIntent.putParcelableArrayListExtra,
|
||||
javaString ("android.intent.extra.STREAM").get(),
|
||||
fileUris.get());
|
||||
|
||||
return doIntent (intent, callback);
|
||||
}
|
||||
|
||||
std::unique_ptr<ActivityLauncher> shareText (const String& text,
|
||||
std::function<void (bool)> callback)
|
||||
{
|
||||
// This function should be called from the main thread, but must not race with singleton
|
||||
// access from other threads.
|
||||
const ScopedLock lock { mutex };
|
||||
|
||||
if (! isContentSharingEnabled())
|
||||
{
|
||||
// You need to enable "Content Sharing" in Projucer's Android exporter.
|
||||
jassertfalse;
|
||||
NullCheckedInvocation::invoke (callback, false);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> intent (env->NewObject (AndroidIntent, AndroidIntent.constructor));
|
||||
env->CallObjectMethod (intent,
|
||||
AndroidIntent.setAction,
|
||||
javaString ("android.intent.action.SEND").get());
|
||||
env->CallObjectMethod (intent,
|
||||
AndroidIntent.putExtra,
|
||||
javaString ("android.intent.extra.TEXT").get(),
|
||||
javaString (text).get());
|
||||
env->CallObjectMethod (intent, AndroidIntent.setType, javaString ("text/plain").get());
|
||||
|
||||
return doIntent (intent, callback);
|
||||
}
|
||||
|
||||
static void onBroadcastResultReceive (JNIEnv*, jobject, int requestCode)
|
||||
{
|
||||
getInstance().sharingFinished (requestCode, true);
|
||||
}
|
||||
|
||||
static jobject JNICALL contentSharerQuery (JNIEnv*, jobject contentProvider, jobject uri, jobjectArray projection)
|
||||
{
|
||||
return getInstance().query (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
|
||||
LocalRef<jobject> (static_cast<jobject> (uri)),
|
||||
LocalRef<jobjectArray> (static_cast<jobjectArray> (projection)));
|
||||
}
|
||||
|
||||
static jobject JNICALL contentSharerOpenFile (JNIEnv*, jobject contentProvider, jobject uri, jstring mode)
|
||||
{
|
||||
return getInstance().openFile (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
|
||||
LocalRef<jobject> (static_cast<jobject> (uri)),
|
||||
LocalRef<jstring> (static_cast<jstring> (mode)));
|
||||
}
|
||||
|
||||
static jobjectArray JNICALL contentSharerGetStreamTypes (JNIEnv*, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter)
|
||||
{
|
||||
return getInstance().getStreamTypes (addLocalRefOwner (uri),
|
||||
addLocalRefOwner (mimeTypeFilter));
|
||||
}
|
||||
|
||||
private:
|
||||
ContentSharerGlobalImpl() = default;
|
||||
|
||||
LocalRef<jobject> makeChooser (const LocalRef<jobject>& intent, int request) const
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
const auto text = javaString ("Choose share target");
|
||||
|
||||
if (getAndroidSDKVersion() < 22)
|
||||
return LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent,
|
||||
AndroidIntent.createChooser,
|
||||
intent.get(),
|
||||
text.get()));
|
||||
|
||||
constexpr jint FLAG_UPDATE_CURRENT = 0x08000000;
|
||||
constexpr jint FLAG_IMMUTABLE = 0x04000000;
|
||||
|
||||
const auto context = getAppContext();
|
||||
|
||||
auto* klass = env->FindClass ("com/rmsl/juce/Receiver");
|
||||
const LocalRef<jobject> replyIntent (env->NewObject (AndroidIntent, AndroidIntent.constructorWithContextAndClass, context.get(), klass));
|
||||
getEnv()->CallObjectMethod (replyIntent, AndroidIntent.putExtraInt, javaString ("com.rmsl.juce.JUCE_REQUEST_CODE").get(), request);
|
||||
|
||||
const auto flags = FLAG_UPDATE_CURRENT | (getAndroidSDKVersion() <= 23 ? 0 : FLAG_IMMUTABLE);
|
||||
const LocalRef<jobject> pendingIntent (env->CallStaticObjectMethod (AndroidPendingIntent,
|
||||
AndroidPendingIntent.getBroadcast,
|
||||
context.get(),
|
||||
request,
|
||||
replyIntent.get(),
|
||||
flags));
|
||||
|
||||
return LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent22,
|
||||
AndroidIntent22.createChooser,
|
||||
intent.get(),
|
||||
text.get(),
|
||||
env->CallObjectMethod (pendingIntent,
|
||||
AndroidPendingIntent.getIntentSender)));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
jobject openFile (const LocalRef<jobject>& contentProvider,
|
||||
const LocalRef<jobject>& uri,
|
||||
[[maybe_unused]] const LocalRef<jstring>& mode)
|
||||
{
|
||||
// This function can be called from multiple threads.
|
||||
const ScopedLock lock { mutex };
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
auto uriElements = getContentUriElements (env, uri);
|
||||
|
||||
if (uriElements.file == File())
|
||||
return nullptr;
|
||||
|
||||
return getAssetFileDescriptor (env, contentProvider, uriElements.file);
|
||||
}
|
||||
|
||||
jobject query (const LocalRef<jobject>& contentProvider,
|
||||
const LocalRef<jobject>& uri,
|
||||
const LocalRef<jobjectArray>& projection)
|
||||
{
|
||||
// This function can be called from multiple threads.
|
||||
const ScopedLock lock { mutex };
|
||||
|
||||
StringArray requestedColumns = javaStringArrayToJuce (projection);
|
||||
StringArray supportedColumns = getSupportedColumns();
|
||||
|
||||
StringArray resultColumns;
|
||||
|
||||
for (const auto& col : supportedColumns)
|
||||
{
|
||||
if (requestedColumns.contains (col))
|
||||
resultColumns.add (col);
|
||||
}
|
||||
|
||||
// Unsupported columns were queried, file sharing may fail.
|
||||
if (resultColumns.isEmpty())
|
||||
return nullptr;
|
||||
|
||||
auto resultJavaColumns = juceStringArrayToJava (resultColumns);
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
const auto uriElements = getContentUriElements (env, uri);
|
||||
|
||||
const auto callback = [info = uriElements.file] (auto& ref)
|
||||
{
|
||||
auto& pimplCursors = ContentSharerGlobalImpl::getInstance().cursors;
|
||||
const auto iter = std::lower_bound (pimplCursors.begin(), pimplCursors.end(), &ref, [] (const auto& managed, const auto* ptr)
|
||||
{
|
||||
return managed.get() == ptr;
|
||||
});
|
||||
|
||||
if (iter != pimplCursors.end() && iter->get() == &ref)
|
||||
pimplCursors.erase (iter);
|
||||
};
|
||||
|
||||
auto [iter, inserted] = cursors.emplace (new AndroidContentSharerCursor (env,
|
||||
contentProvider,
|
||||
resultJavaColumns,
|
||||
callback));
|
||||
|
||||
if (uriElements.file == File())
|
||||
return (*iter)->getNativeCursor();
|
||||
|
||||
LocalRef<jobjectArray> values (env->NewObjectArray ((jsize) resultColumns.size(), JavaObject, nullptr));
|
||||
|
||||
for (int i = 0; i < resultColumns.size(); ++i)
|
||||
{
|
||||
if (resultColumns.getReference (i) == "_display_name")
|
||||
{
|
||||
env->SetObjectArrayElement (values, i, javaString (uriElements.filename).get());
|
||||
}
|
||||
else if (resultColumns.getReference (i) == "_size")
|
||||
{
|
||||
LocalRef<jobject> javaFile (env->NewObject (JavaFile,
|
||||
JavaFile.constructor,
|
||||
javaString (uriElements.file.getFullPathName()).get()));
|
||||
|
||||
jlong fileLength = env->CallLongMethod (javaFile, JavaFile.length);
|
||||
|
||||
env->SetObjectArrayElement (values, i, env->NewObject (JavaLong, JavaLong.constructor, fileLength));
|
||||
}
|
||||
}
|
||||
|
||||
(*iter)->addRow (values);
|
||||
return (*iter)->getNativeCursor();
|
||||
}
|
||||
|
||||
jobjectArray getStreamTypes (const LocalRef<jobject>& uri, const LocalRef<jstring>& mimeTypeFilter)
|
||||
{
|
||||
// This function can be called from multiple threads.
|
||||
const ScopedLock lock { mutex };
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
auto extension = getContentUriElements (env, uri).filename.fromLastOccurrenceOf (".", false, true);
|
||||
|
||||
if (extension.isEmpty())
|
||||
return nullptr;
|
||||
|
||||
return juceStringArrayToJava (filterMimeTypes (detail::MimeTypeTable::getMimeTypesForFileExtension (extension),
|
||||
juceString (mimeTypeFilter.get()))).release();
|
||||
}
|
||||
|
||||
std::unique_ptr<ActivityLauncher> doIntent (const LocalRef<jobject>& intent,
|
||||
std::function<void (bool)> callback)
|
||||
{
|
||||
static std::atomic<int> lastRequest = 1003;
|
||||
const auto requestCode = lastRequest++;
|
||||
callbackForRequest.emplace (requestCode, callback);
|
||||
const auto chooser = makeChooser (intent, requestCode);
|
||||
|
||||
auto launcher = std::make_unique<ActivityLauncher> (chooser, requestCode);
|
||||
launcher->callback = [] (int request, int resultCode, LocalRef<jobject>)
|
||||
{
|
||||
ContentSharerGlobalImpl::getInstance().sharingFinished (request, resultCode == -1);
|
||||
};
|
||||
launcher->open();
|
||||
return launcher;
|
||||
}
|
||||
|
||||
void sharingFinished (int request, bool succeeded)
|
||||
{
|
||||
// This function should be called from the main thread, but must not race with singleton
|
||||
// access from other threads.
|
||||
const ScopedLock lock { mutex };
|
||||
|
||||
const auto iter = callbackForRequest.find (request);
|
||||
|
||||
if (iter == callbackForRequest.end())
|
||||
return;
|
||||
|
||||
const ScopeGuard scope { [&] { callbackForRequest.erase (iter); } };
|
||||
|
||||
if (iter->second == nullptr)
|
||||
return;
|
||||
|
||||
iter->second (succeeded);
|
||||
}
|
||||
|
||||
bool isContentSharingEnabled() const
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> packageManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager));
|
||||
|
||||
constexpr int getProviders = 8;
|
||||
LocalRef<jobject> packageInfo (env->CallObjectMethod (packageManager,
|
||||
AndroidPackageManager.getPackageInfo,
|
||||
javaString (packageName).get(),
|
||||
getProviders));
|
||||
LocalRef<jobjectArray> providers ((jobjectArray) env->GetObjectField (packageInfo,
|
||||
AndroidPackageInfo.providers));
|
||||
|
||||
if (providers == nullptr)
|
||||
return false;
|
||||
|
||||
auto sharingContentProviderAuthority = packageName + ".sharingcontentprovider";
|
||||
const int numProviders = env->GetArrayLength (providers.get());
|
||||
|
||||
for (int i = 0; i < numProviders; ++i)
|
||||
{
|
||||
LocalRef<jobject> providerInfo (env->GetObjectArrayElement (providers, i));
|
||||
LocalRef<jstring> authority ((jstring) env->GetObjectField (providerInfo, AndroidProviderInfo.authority));
|
||||
|
||||
if (juceString (authority) == sharingContentProviderAuthority)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ContentUriElements
|
||||
{
|
||||
String filename;
|
||||
File file;
|
||||
};
|
||||
|
||||
ContentUriElements getContentUriElements (JNIEnv* env, const LocalRef<jobject>& uri) const
|
||||
{
|
||||
const auto fullUri = juceString ((jstring) env->CallObjectMethod (uri.get(), AndroidUri.toString));
|
||||
|
||||
const auto filename = fullUri.fromLastOccurrenceOf ("/", false, true);
|
||||
|
||||
const auto iter = fileForUri.find (fullUri);
|
||||
const auto info = iter != fileForUri.end() ? iter->second : File{};
|
||||
|
||||
return { filename, info };
|
||||
}
|
||||
|
||||
static StringArray getSupportedColumns()
|
||||
{
|
||||
return StringArray ("_display_name", "_size");
|
||||
}
|
||||
|
||||
jobject getAssetFileDescriptor (JNIEnv* env, const LocalRef<jobject>& contentProvider, const File& filepath)
|
||||
{
|
||||
if (nonAssetFilePathsPendingShare.find (filepath) == nonAssetFilePathsPendingShare.end())
|
||||
{
|
||||
const auto onCloseCallback = [filepath]
|
||||
{
|
||||
ContentSharerGlobalImpl::getInstance().nonAssetFilePathsPendingShare.erase (filepath);
|
||||
};
|
||||
|
||||
auto observer = rawToUniquePtr (new AndroidContentSharerFileObserver (env,
|
||||
contentProvider,
|
||||
filepath,
|
||||
onCloseCallback));
|
||||
|
||||
nonAssetFilePathsPendingShare.emplace (filepath, std::move (observer));
|
||||
}
|
||||
|
||||
const LocalRef<jobject> javaFile (env->NewObject (JavaFile,
|
||||
JavaFile.constructor,
|
||||
javaString (filepath.getFullPathName()).get()));
|
||||
|
||||
constexpr int modeReadOnly = 268435456;
|
||||
LocalRef<jobject> parcelFileDescriptor (env->CallStaticObjectMethod (ParcelFileDescriptor,
|
||||
ParcelFileDescriptor.open,
|
||||
javaFile.get(),
|
||||
modeReadOnly));
|
||||
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to create file descriptor. Have you provided a valid file path/resource name?
|
||||
jassertfalse;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jlong startOffset = 0;
|
||||
jlong unknownLength = -1;
|
||||
|
||||
assetFileDescriptors.add (GlobalRef (LocalRef<jobject> (env->NewObject (AssetFileDescriptor,
|
||||
AssetFileDescriptor.constructor,
|
||||
parcelFileDescriptor.get(),
|
||||
startOffset,
|
||||
unknownLength))));
|
||||
|
||||
return assetFileDescriptors.getReference (assetFileDescriptors.size() - 1).get();
|
||||
}
|
||||
|
||||
StringArray filterMimeTypes (const StringArray& mimeTypes, const String& filter)
|
||||
{
|
||||
String filterToUse (filter.removeCharacters ("*"));
|
||||
|
||||
if (filterToUse.isEmpty() || filterToUse == "/")
|
||||
return mimeTypes;
|
||||
|
||||
StringArray result;
|
||||
|
||||
for (const auto& type : mimeTypes)
|
||||
if (String (type).contains (filterToUse))
|
||||
result.add (type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static String getCommonMimeType (const StringArray& mimeTypes)
|
||||
{
|
||||
if (mimeTypes.isEmpty())
|
||||
return "*/*";
|
||||
|
||||
auto commonMime = mimeTypes[0];
|
||||
bool lookForCommonGroup = false;
|
||||
|
||||
for (int i = 1; i < mimeTypes.size(); ++i)
|
||||
{
|
||||
if (mimeTypes[i] == commonMime)
|
||||
continue;
|
||||
|
||||
if (! lookForCommonGroup)
|
||||
{
|
||||
lookForCommonGroup = true;
|
||||
commonMime = commonMime.upToFirstOccurrenceOf ("/", true, false);
|
||||
}
|
||||
|
||||
if (! mimeTypes[i].startsWith (commonMime))
|
||||
return "*/*";
|
||||
}
|
||||
|
||||
return lookForCommonGroup ? commonMime + "*" : commonMime;
|
||||
}
|
||||
|
||||
CriticalSection mutex;
|
||||
Array<GlobalRef> assetFileDescriptors;
|
||||
std::map<File, std::unique_ptr<AndroidContentSharerFileObserver>> nonAssetFilePathsPendingShare;
|
||||
std::set<std::unique_ptr<AndroidContentSharerCursor>> cursors;
|
||||
std::map<String, File> fileForUri;
|
||||
std::map<int, std::function<void (bool)>> callbackForRequest;
|
||||
};
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
CALLBACK (ContentSharerGlobalImpl::contentSharerQuery, "contentSharerQuery", "(Landroid/net/Uri;[Ljava/lang/String;)Landroid/database/Cursor;") \
|
||||
CALLBACK (ContentSharerGlobalImpl::contentSharerOpenFile, "contentSharerOpenFile", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;") \
|
||||
CALLBACK (ContentSharerGlobalImpl::contentSharerGetStreamTypes, "contentSharerGetStreamTypes", "(Landroid/net/Uri;Ljava/lang/String;)[Ljava/lang/String;") \
|
||||
|
||||
DECLARE_JNI_CLASS (JuceSharingContentProvider, "com/rmsl/juce/JuceSharingContentProvider")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
CALLBACK (ContentSharerGlobalImpl::onBroadcastResultReceive, "onBroadcastResultNative", "(I)V")
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidReceiver, "com/rmsl/juce/Receiver")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
//==============================================================================
|
||||
class AndroidContentSharerPrepareFilesTask final : private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
AndroidContentSharerPrepareFilesTask (const Array<URL>& fileUrls,
|
||||
std::function<void (const std::map<String, File>&, const StringArray&)> onCompletionIn)
|
||||
: onCompletion (std::move (onCompletionIn)),
|
||||
task (std::async (std::launch::async, [this, fileUrls]
|
||||
{
|
||||
run (fileUrls);
|
||||
triggerAsyncUpdate();
|
||||
})) {}
|
||||
|
||||
~AndroidContentSharerPrepareFilesTask() override
|
||||
{
|
||||
task.wait();
|
||||
cancelPendingUpdate();
|
||||
}
|
||||
|
||||
private:
|
||||
const String packageName = ContentSharerGlobalImpl::getInstance().packageName;
|
||||
const String uriBase = ContentSharerGlobalImpl::getInstance().uriBase;
|
||||
|
||||
struct StreamCloser
|
||||
{
|
||||
explicit StreamCloser (const LocalRef<jobject>& streamToUse)
|
||||
: stream (GlobalRef (streamToUse))
|
||||
{
|
||||
}
|
||||
|
||||
~StreamCloser()
|
||||
{
|
||||
if (stream.get() != nullptr)
|
||||
getEnv()->CallVoidMethod (stream, JavaCloseable.close);
|
||||
}
|
||||
|
||||
GlobalRef stream;
|
||||
};
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
onCompletion (infoForUri, mimeTypes);
|
||||
}
|
||||
|
||||
void run (const Array<URL>& fileUrls)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
StringArray filePaths;
|
||||
|
||||
for (const auto& f : fileUrls)
|
||||
{
|
||||
const auto scheme = f.getScheme();
|
||||
|
||||
// Only "file://" scheme or no scheme (for files in app bundle) are allowed!
|
||||
jassert (scheme.isEmpty() || scheme == "file");
|
||||
|
||||
const auto fileToPrepare = [&]
|
||||
{
|
||||
if (! scheme.isEmpty())
|
||||
return f;
|
||||
|
||||
// Raw resource names need to be all lower case
|
||||
jassert (f.toString (true).toLowerCase() == f.toString (true));
|
||||
|
||||
// This will get us a file with file:// URI
|
||||
return copyAssetFileToTemporaryFile (env, f.toString (true));
|
||||
}();
|
||||
|
||||
if (fileToPrepare.isEmpty())
|
||||
continue;
|
||||
|
||||
const auto filepath = URL::removeEscapeChars (fileToPrepare.toString (true).fromFirstOccurrenceOf ("file://", false, false));
|
||||
|
||||
filePaths.add (filepath);
|
||||
}
|
||||
|
||||
std::vector<String> extensions;
|
||||
|
||||
for (const auto& filepath : filePaths)
|
||||
{
|
||||
const auto filename = filepath.fromLastOccurrenceOf ("/", false, true);
|
||||
extensions.push_back (filename.fromLastOccurrenceOf (".", false, true));
|
||||
}
|
||||
|
||||
std::set<String> mimes;
|
||||
|
||||
if (std::none_of (extensions.begin(), extensions.end(), [] (const String& s) { return s.isEmpty(); }))
|
||||
for (const auto& extension : extensions)
|
||||
for (const auto& mime : detail::MimeTypeTable::getMimeTypesForFileExtension (extension))
|
||||
mimes.insert (mime);
|
||||
|
||||
for (const auto& mime : mimes)
|
||||
mimeTypes.add (mime);
|
||||
|
||||
for (auto it = filePaths.begin(); it != filePaths.end(); ++it)
|
||||
{
|
||||
const auto filename = it->fromLastOccurrenceOf ("/", false, true);
|
||||
const auto contentString = uriBase + String (std::distance (filePaths.begin(), it)) + "/" + filename;
|
||||
infoForUri.emplace (contentString, *it);
|
||||
}
|
||||
}
|
||||
|
||||
URL copyAssetFileToTemporaryFile (JNIEnv* env, const String& filename)
|
||||
{
|
||||
LocalRef<jobject> resources (env->CallObjectMethod (getAppContext().get(), AndroidContext.getResources));
|
||||
int fileId = env->CallIntMethod (resources,
|
||||
AndroidResources.getIdentifier,
|
||||
javaString (filename).get(),
|
||||
javaString ("raw").get(),
|
||||
javaString (packageName).get());
|
||||
|
||||
// Raw resource not found. Please make sure that you include your file as a raw resource
|
||||
// and that you specify just the file name, without an extension.
|
||||
jassert (fileId != 0);
|
||||
|
||||
if (fileId == 0)
|
||||
return {};
|
||||
|
||||
LocalRef<jobject> assetFd (env->CallObjectMethod (resources,
|
||||
AndroidResources.openRawResourceFd,
|
||||
fileId));
|
||||
|
||||
StreamCloser inputStream (LocalRef<jobject> (env->CallObjectMethod (assetFd, AssetFileDescriptor.createInputStream)));
|
||||
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to open file stream for resource
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto tempFile = File::createTempFile ({});
|
||||
tempFile.createDirectory();
|
||||
tempFile = tempFile.getChildFile (filename);
|
||||
|
||||
StreamCloser outputStream (LocalRef<jobject> (env->NewObject (JavaFileOutputStream,
|
||||
JavaFileOutputStream.constructor,
|
||||
javaString (tempFile.getFullPathName()).get())));
|
||||
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to open file stream for temporary file
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
LocalRef<jbyteArray> buffer (env->NewByteArray (1024));
|
||||
int bytesRead = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
bytesRead = env->CallIntMethod (inputStream.stream, JavaFileInputStream.read, buffer.get());
|
||||
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to read from resource file.
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (bytesRead < 0)
|
||||
break;
|
||||
|
||||
env->CallVoidMethod (outputStream.stream, JavaFileOutputStream.write, buffer.get(), 0, bytesRead);
|
||||
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to write to temporary file.
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return URL (tempFile);
|
||||
}
|
||||
|
||||
std::map<String, File> infoForUri;
|
||||
StringArray mimeTypes;
|
||||
std::function<void (const std::map<String, File>&, const StringArray&)> onCompletion;
|
||||
// This task is obtained from std::async(). Its destructor will block until the asynchronous
|
||||
// task has completed; as a result, we can guarantee that the async task will have finished
|
||||
// before the lifetimes of the other data members and base class end.
|
||||
std::future<void> task;
|
||||
};
|
||||
|
||||
auto detail::ScopedContentSharerInterface::shareFiles (const Array<URL>& urls, Component*) -> std::unique_ptr<ScopedContentSharerInterface>
|
||||
{
|
||||
class NativeScopedContentSharerInterface final : public detail::ScopedContentSharerInterface
|
||||
{
|
||||
public:
|
||||
explicit NativeScopedContentSharerInterface (Array<URL> f)
|
||||
: files (std::move (f)) {}
|
||||
|
||||
void runAsync (ContentSharer::Callback callback) override
|
||||
{
|
||||
// This lambda will only be called if the AndroidContentSharerPrepareFilesTask is still
|
||||
// alive. We know that our lifetime will end after the
|
||||
// AndroidContentSharerPrepareFilesTask, so there's no need to check that 'this' is
|
||||
// still valid inside the lambda.
|
||||
task.emplace (files, [this, callback] (const std::map<String, File>& infoForUri, const StringArray& mimeTypes)
|
||||
{
|
||||
launcher = ContentSharerGlobalImpl::getInstance().sharePreparedFiles (infoForUri, mimeTypes, [callback] (bool success)
|
||||
{
|
||||
callback (success, {});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
// dismiss() doesn't close the sharesheet, and there doesn't seem to be any alternative
|
||||
// Maybe this will work in the future...
|
||||
launcher.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
Array<URL> files;
|
||||
std::optional<AndroidContentSharerPrepareFilesTask> task;
|
||||
std::unique_ptr<ActivityLauncher> launcher;
|
||||
};
|
||||
|
||||
return std::make_unique<NativeScopedContentSharerInterface> (std::move (urls));
|
||||
}
|
||||
|
||||
auto detail::ScopedContentSharerInterface::shareText (const String& text, Component*) -> std::unique_ptr<ScopedContentSharerInterface>
|
||||
{
|
||||
class NativeScopedContentSharerInterface final : public detail::ScopedContentSharerInterface
|
||||
{
|
||||
public:
|
||||
explicit NativeScopedContentSharerInterface (String t)
|
||||
: text (std::move (t)) {}
|
||||
|
||||
void runAsync (ContentSharer::Callback callback) override
|
||||
{
|
||||
launcher = ContentSharerGlobalImpl::getInstance().shareText (text, [callback] (bool success)
|
||||
{
|
||||
callback (success, {});
|
||||
});
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
// dismiss() doesn't close the sharesheet, and there doesn't seem to be any alternative
|
||||
// Maybe this will work in the future...
|
||||
launcher.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
String text;
|
||||
std::unique_ptr<ActivityLauncher> launcher;
|
||||
};
|
||||
|
||||
return std::make_unique<NativeScopedContentSharerInterface> (std::move (text));
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class NativeScopedContentSharerInterface final : public detail::ScopedContentSharerInterface,
|
||||
public detail::NativeModalWrapperComponent
|
||||
{
|
||||
public:
|
||||
NativeScopedContentSharerInterface (Component* parentIn, NSUniquePtr<NSArray> itemsIn)
|
||||
: parent (parentIn), items (std::move (itemsIn)) {}
|
||||
|
||||
void runAsync (std::function<void (bool, const String&)> callback) override
|
||||
{
|
||||
if ([items.get() count] == 0)
|
||||
{
|
||||
jassertfalse;
|
||||
NullCheckedInvocation::invoke (callback, false, "No valid items found for sharing.");
|
||||
return;
|
||||
}
|
||||
|
||||
controller.reset ([[UIActivityViewController alloc] initWithActivityItems: items.get()
|
||||
applicationActivities: nil]);
|
||||
|
||||
controller.get().excludedActivityTypes = nil;
|
||||
|
||||
controller.get().completionWithItemsHandler = ^([[maybe_unused]] UIActivityType type, BOOL completed,
|
||||
[[maybe_unused]] NSArray* returnedItems, NSError* error)
|
||||
{
|
||||
const auto errorDescription = error != nil ? nsStringToJuce ([error localizedDescription])
|
||||
: String();
|
||||
exitModalState (0);
|
||||
|
||||
NullCheckedInvocation::invoke (callback, completed && errorDescription.isEmpty(), errorDescription);
|
||||
};
|
||||
|
||||
displayNativeWindowModally (parent);
|
||||
|
||||
enterModalState (true, nullptr, false);
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
[controller.get() dismissViewControllerAnimated: YES completion: nil];
|
||||
}
|
||||
|
||||
private:
|
||||
UIViewController* getViewController() const override { return controller.get(); }
|
||||
|
||||
Component* parent = nullptr;
|
||||
NSUniquePtr<UIActivityViewController> controller;
|
||||
NSUniquePtr<NSArray> items;
|
||||
};
|
||||
|
||||
auto detail::ScopedContentSharerInterface::shareFiles (const Array<URL>& files, Component* parent) -> std::unique_ptr<ScopedContentSharerInterface>
|
||||
{
|
||||
NSUniquePtr<NSMutableArray> urls ([[NSMutableArray arrayWithCapacity: (NSUInteger) files.size()] retain]);
|
||||
|
||||
for (const auto& f : files)
|
||||
{
|
||||
NSString* nativeFilePath = nil;
|
||||
|
||||
if (f.isLocalFile())
|
||||
{
|
||||
nativeFilePath = juceStringToNS (f.getLocalFile().getFullPathName());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto filePath = f.toString (false);
|
||||
|
||||
auto* fileDirectory = filePath.contains ("/")
|
||||
? juceStringToNS (filePath.upToLastOccurrenceOf ("/", false, false))
|
||||
: [NSString string];
|
||||
|
||||
auto fileName = juceStringToNS (filePath.fromLastOccurrenceOf ("/", false, false)
|
||||
.upToLastOccurrenceOf (".", false, false));
|
||||
|
||||
auto fileExt = juceStringToNS (filePath.fromLastOccurrenceOf (".", false, false));
|
||||
|
||||
if ([fileDirectory length] == NSUInteger (0))
|
||||
nativeFilePath = [[NSBundle mainBundle] pathForResource: fileName
|
||||
ofType: fileExt];
|
||||
else
|
||||
nativeFilePath = [[NSBundle mainBundle] pathForResource: fileName
|
||||
ofType: fileExt
|
||||
inDirectory: fileDirectory];
|
||||
}
|
||||
|
||||
if (nativeFilePath != nil)
|
||||
[urls.get() addObject: [NSURL fileURLWithPath: nativeFilePath]];
|
||||
}
|
||||
|
||||
return std::make_unique<NativeScopedContentSharerInterface> (parent, std::move (urls));
|
||||
}
|
||||
|
||||
auto detail::ScopedContentSharerInterface::shareText (const String& text, Component* parent) -> std::unique_ptr<ScopedContentSharerInterface>
|
||||
{
|
||||
NSUniquePtr<NSArray> array ([[NSArray arrayWithObject: juceStringToNS (text)] retain]);
|
||||
return std::make_unique<NativeScopedContentSharerInterface> (parent, std::move (array));
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,609 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static Cursor createDraggingHandCursor();
|
||||
ComponentPeer* getPeerFor (::Window);
|
||||
|
||||
//==============================================================================
|
||||
class X11DragState
|
||||
{
|
||||
public:
|
||||
X11DragState() = default;
|
||||
|
||||
//==============================================================================
|
||||
bool isDragging() const noexcept
|
||||
{
|
||||
return dragging;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleExternalSelectionClear()
|
||||
{
|
||||
if (dragging)
|
||||
externalResetDragAndDrop();
|
||||
}
|
||||
|
||||
void handleExternalSelectionRequest (const XEvent& evt)
|
||||
{
|
||||
auto targetType = evt.xselectionrequest.target;
|
||||
|
||||
XEvent s;
|
||||
s.xselection.type = SelectionNotify;
|
||||
s.xselection.requestor = evt.xselectionrequest.requestor;
|
||||
s.xselection.selection = evt.xselectionrequest.selection;
|
||||
s.xselection.target = targetType;
|
||||
s.xselection.property = None;
|
||||
s.xselection.time = evt.xselectionrequest.time;
|
||||
|
||||
auto* display = getDisplay();
|
||||
|
||||
if (allowedTypes.contains (targetType))
|
||||
{
|
||||
s.xselection.property = evt.xselectionrequest.property;
|
||||
|
||||
X11Symbols::getInstance()->xChangeProperty (display, evt.xselectionrequest.requestor, evt.xselectionrequest.property,
|
||||
targetType, 8, PropModeReplace,
|
||||
reinterpret_cast<const unsigned char*> (textOrFiles.toRawUTF8()),
|
||||
(int) textOrFiles.getNumBytesAsUTF8());
|
||||
}
|
||||
|
||||
X11Symbols::getInstance()->xSendEvent (display, evt.xselectionrequest.requestor, True, 0, &s);
|
||||
}
|
||||
|
||||
void handleExternalDragAndDropStatus (const XClientMessageEvent& clientMsg)
|
||||
{
|
||||
if (expectingStatus)
|
||||
{
|
||||
expectingStatus = false;
|
||||
canDrop = false;
|
||||
silentRect = {};
|
||||
|
||||
const auto& atoms = getAtoms();
|
||||
|
||||
if ((clientMsg.data.l[1] & 1) != 0
|
||||
&& ((Atom) clientMsg.data.l[4] == atoms.XdndActionCopy
|
||||
|| (Atom) clientMsg.data.l[4] == atoms.XdndActionPrivate))
|
||||
{
|
||||
if ((clientMsg.data.l[1] & 2) == 0) // target requests silent rectangle
|
||||
silentRect.setBounds ((int) clientMsg.data.l[2] >> 16, (int) clientMsg.data.l[2] & 0xffff,
|
||||
(int) clientMsg.data.l[3] >> 16, (int) clientMsg.data.l[3] & 0xffff);
|
||||
|
||||
canDrop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleExternalDragButtonReleaseEvent()
|
||||
{
|
||||
if (dragging)
|
||||
X11Symbols::getInstance()->xUngrabPointer (getDisplay(), CurrentTime);
|
||||
|
||||
if (canDrop)
|
||||
{
|
||||
sendExternalDragAndDropDrop();
|
||||
}
|
||||
else
|
||||
{
|
||||
sendExternalDragAndDropLeave();
|
||||
externalResetDragAndDrop();
|
||||
}
|
||||
}
|
||||
|
||||
void handleExternalDragMotionNotify()
|
||||
{
|
||||
auto* display = getDisplay();
|
||||
|
||||
auto newTargetWindow = externalFindDragTargetWindow (X11Symbols::getInstance()
|
||||
->xRootWindow (display,
|
||||
X11Symbols::getInstance()->xDefaultScreen (display)));
|
||||
|
||||
if (targetWindow != newTargetWindow)
|
||||
{
|
||||
if (targetWindow != None)
|
||||
sendExternalDragAndDropLeave();
|
||||
|
||||
canDrop = false;
|
||||
silentRect = {};
|
||||
|
||||
if (newTargetWindow == None)
|
||||
return;
|
||||
|
||||
xdndVersion = getDnDVersionForWindow (newTargetWindow);
|
||||
|
||||
if (xdndVersion == -1)
|
||||
return;
|
||||
|
||||
targetWindow = newTargetWindow;
|
||||
sendExternalDragAndDropEnter();
|
||||
}
|
||||
|
||||
if (! expectingStatus)
|
||||
sendExternalDragAndDropPosition();
|
||||
}
|
||||
|
||||
void handleDragAndDropPosition (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
|
||||
{
|
||||
if (dragAndDropSourceWindow == 0)
|
||||
return;
|
||||
|
||||
dragAndDropSourceWindow = (::Window) clientMsg.data.l[0];
|
||||
|
||||
if (windowH == 0)
|
||||
windowH = (::Window) peer->getNativeHandle();
|
||||
|
||||
const auto displays = Desktop::getInstance().getDisplays();
|
||||
const auto logicalPos = displays.physicalToLogical (Point<int> ((int) clientMsg.data.l[2] >> 16,
|
||||
(int) clientMsg.data.l[2] & 0xffff));
|
||||
const auto dropPos = detail::ScalingHelpers::screenPosToLocalPos (peer->getComponent(), logicalPos.toFloat()).roundToInt();
|
||||
|
||||
const auto& atoms = getAtoms();
|
||||
|
||||
auto targetAction = atoms.XdndActionCopy;
|
||||
|
||||
for (int i = numElementsInArray (atoms.allowedActions); --i >= 0;)
|
||||
{
|
||||
if ((Atom) clientMsg.data.l[4] == atoms.allowedActions[i])
|
||||
{
|
||||
targetAction = atoms.allowedActions[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sendDragAndDropStatus (true, targetAction);
|
||||
|
||||
if (dragInfo.position != dropPos)
|
||||
{
|
||||
dragInfo.position = dropPos;
|
||||
|
||||
if (dragInfo.isEmpty())
|
||||
updateDraggedFileList (clientMsg, (::Window) peer->getNativeHandle());
|
||||
|
||||
if (! dragInfo.isEmpty())
|
||||
peer->handleDragMove (dragInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void handleDragAndDropDrop (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
|
||||
{
|
||||
if (dragInfo.isEmpty())
|
||||
{
|
||||
// no data, transaction finished in handleDragAndDropSelection()
|
||||
finishAfterDropDataReceived = true;
|
||||
updateDraggedFileList (clientMsg, (::Window) peer->getNativeHandle());
|
||||
}
|
||||
else
|
||||
{
|
||||
handleDragAndDropDataReceived(); // data was already received
|
||||
}
|
||||
}
|
||||
|
||||
void handleDragAndDropEnter (const XClientMessageEvent& clientMsg, ComponentPeer* peer)
|
||||
{
|
||||
dragInfo.clear();
|
||||
srcMimeTypeAtomList.clear();
|
||||
|
||||
dragAndDropCurrentMimeType = 0;
|
||||
auto dndCurrentVersion = (static_cast<unsigned long> (clientMsg.data.l[1]) & 0xff000000) >> 24;
|
||||
|
||||
if (dndCurrentVersion < 3 || dndCurrentVersion > XWindowSystemUtilities::Atoms::DndVersion)
|
||||
{
|
||||
dragAndDropSourceWindow = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& atoms = getAtoms();
|
||||
|
||||
dragAndDropSourceWindow = (::Window) clientMsg.data.l[0];
|
||||
|
||||
if ((clientMsg.data.l[1] & 1) != 0)
|
||||
{
|
||||
XWindowSystemUtilities::ScopedXLock xLock;
|
||||
|
||||
XWindowSystemUtilities::GetXProperty prop (getDisplay(),
|
||||
dragAndDropSourceWindow,
|
||||
atoms.XdndTypeList,
|
||||
0,
|
||||
0x8000000L,
|
||||
false,
|
||||
XA_ATOM);
|
||||
|
||||
if (prop.success && prop.actualType == XA_ATOM && prop.actualFormat == 32 && prop.numItems != 0)
|
||||
{
|
||||
auto* types = prop.data;
|
||||
|
||||
for (unsigned long i = 0; i < prop.numItems; ++i)
|
||||
{
|
||||
unsigned long type;
|
||||
memcpy (&type, types, sizeof (unsigned long));
|
||||
|
||||
if (type != None)
|
||||
srcMimeTypeAtomList.add (type);
|
||||
|
||||
types += sizeof (unsigned long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (srcMimeTypeAtomList.isEmpty())
|
||||
{
|
||||
for (int i = 2; i < 5; ++i)
|
||||
if (clientMsg.data.l[i] != None)
|
||||
srcMimeTypeAtomList.add ((unsigned long) clientMsg.data.l[i]);
|
||||
|
||||
if (srcMimeTypeAtomList.isEmpty())
|
||||
{
|
||||
dragAndDropSourceWindow = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < srcMimeTypeAtomList.size() && dragAndDropCurrentMimeType == 0; ++i)
|
||||
for (int j = 0; j < numElementsInArray (atoms.allowedMimeTypes); ++j)
|
||||
if (srcMimeTypeAtomList[i] == atoms.allowedMimeTypes[j])
|
||||
dragAndDropCurrentMimeType = atoms.allowedMimeTypes[j];
|
||||
|
||||
handleDragAndDropPosition (clientMsg, peer);
|
||||
}
|
||||
|
||||
void handleDragAndDropExit()
|
||||
{
|
||||
if (auto* peer = getPeerFor (windowH))
|
||||
peer->handleDragExit (dragInfo);
|
||||
|
||||
resetDragAndDrop();
|
||||
}
|
||||
|
||||
void handleDragAndDropSelection (const XEvent& evt)
|
||||
{
|
||||
dragInfo.clear();
|
||||
|
||||
if (evt.xselection.property != None)
|
||||
{
|
||||
StringArray lines;
|
||||
|
||||
{
|
||||
MemoryBlock dropData;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
XWindowSystemUtilities::GetXProperty prop (getDisplay(),
|
||||
evt.xany.window,
|
||||
evt.xselection.property,
|
||||
(long) (dropData.getSize() / 4),
|
||||
65536,
|
||||
false,
|
||||
AnyPropertyType);
|
||||
|
||||
if (! prop.success)
|
||||
break;
|
||||
|
||||
dropData.append (prop.data, (size_t) (prop.actualFormat / 8) * prop.numItems);
|
||||
|
||||
if (prop.bytesLeft <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
lines.addLines (dropData.toString());
|
||||
}
|
||||
|
||||
if (XWindowSystemUtilities::Atoms::isMimeTypeFile (getDisplay(), dragAndDropCurrentMimeType))
|
||||
{
|
||||
for (const auto& line : lines)
|
||||
{
|
||||
const auto escaped = line.replace ("+", "%2B").replace ("file://", String(), true);
|
||||
dragInfo.files.add (URL::removeEscapeChars (escaped));
|
||||
}
|
||||
|
||||
dragInfo.files.trim();
|
||||
dragInfo.files.removeEmptyStrings();
|
||||
}
|
||||
else
|
||||
{
|
||||
dragInfo.text = lines.joinIntoString ("\n");
|
||||
}
|
||||
|
||||
if (finishAfterDropDataReceived)
|
||||
handleDragAndDropDataReceived();
|
||||
}
|
||||
}
|
||||
|
||||
void externalResetDragAndDrop()
|
||||
{
|
||||
if (dragging)
|
||||
{
|
||||
XWindowSystemUtilities::ScopedXLock xLock;
|
||||
X11Symbols::getInstance()->xUngrabPointer (getDisplay(), CurrentTime);
|
||||
}
|
||||
|
||||
NullCheckedInvocation::invoke (completionCallback);
|
||||
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
bool externalDragInit (::Window window, bool text, const String& str, std::function<void()>&& cb)
|
||||
{
|
||||
windowH = window;
|
||||
isText = text;
|
||||
textOrFiles = str;
|
||||
targetWindow = windowH;
|
||||
completionCallback = std::move (cb);
|
||||
|
||||
auto* display = getDisplay();
|
||||
|
||||
allowedTypes.add (XWindowSystemUtilities::Atoms::getCreating (display, isText ? "text/plain" : "text/uri-list"));
|
||||
|
||||
auto pointerGrabMask = (unsigned int) (Button1MotionMask | ButtonReleaseMask);
|
||||
|
||||
XWindowSystemUtilities::ScopedXLock xLock;
|
||||
|
||||
if (X11Symbols::getInstance()->xGrabPointer (display, windowH, True, pointerGrabMask,
|
||||
GrabModeAsync, GrabModeAsync, None, None, CurrentTime) == GrabSuccess)
|
||||
{
|
||||
const auto& atoms = getAtoms();
|
||||
|
||||
// No other method of changing the pointer seems to work, this call is needed from this very context
|
||||
X11Symbols::getInstance()->xChangeActivePointerGrab (display, pointerGrabMask, (Cursor) createDraggingHandCursor(), CurrentTime);
|
||||
|
||||
X11Symbols::getInstance()->xSetSelectionOwner (display, atoms.XdndSelection, windowH, CurrentTime);
|
||||
|
||||
// save the available types to XdndTypeList
|
||||
X11Symbols::getInstance()->xChangeProperty (display, windowH, atoms.XdndTypeList, XA_ATOM, 32, PropModeReplace,
|
||||
reinterpret_cast<const unsigned char*> (allowedTypes.getRawDataPointer()), allowedTypes.size());
|
||||
|
||||
dragging = true;
|
||||
xdndVersion = getDnDVersionForWindow (targetWindow);
|
||||
|
||||
sendExternalDragAndDropEnter();
|
||||
handleExternalDragMotionNotify();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
const XWindowSystemUtilities::Atoms& getAtoms() const noexcept { return XWindowSystem::getInstance()->getAtoms(); }
|
||||
::Display* getDisplay() const noexcept { return XWindowSystem::getInstance()->getDisplay(); }
|
||||
|
||||
//==============================================================================
|
||||
void sendDragAndDropMessage (XClientMessageEvent& msg)
|
||||
{
|
||||
auto* display = getDisplay();
|
||||
|
||||
msg.type = ClientMessage;
|
||||
msg.display = display;
|
||||
msg.window = dragAndDropSourceWindow;
|
||||
msg.format = 32;
|
||||
msg.data.l[0] = (long) windowH;
|
||||
|
||||
XWindowSystemUtilities::ScopedXLock xLock;
|
||||
X11Symbols::getInstance()->xSendEvent (display, dragAndDropSourceWindow, False, 0, (XEvent*) &msg);
|
||||
}
|
||||
|
||||
bool sendExternalDragAndDropMessage (XClientMessageEvent& msg)
|
||||
{
|
||||
auto* display = getDisplay();
|
||||
|
||||
msg.type = ClientMessage;
|
||||
msg.display = display;
|
||||
msg.window = targetWindow;
|
||||
msg.format = 32;
|
||||
msg.data.l[0] = (long) windowH;
|
||||
|
||||
XWindowSystemUtilities::ScopedXLock xLock;
|
||||
return X11Symbols::getInstance()->xSendEvent (display, targetWindow, False, 0, (XEvent*) &msg) != 0;
|
||||
}
|
||||
|
||||
void sendExternalDragAndDropDrop()
|
||||
{
|
||||
XClientMessageEvent msg;
|
||||
zerostruct (msg);
|
||||
|
||||
msg.message_type = getAtoms().XdndDrop;
|
||||
msg.data.l[2] = CurrentTime;
|
||||
|
||||
sendExternalDragAndDropMessage (msg);
|
||||
}
|
||||
|
||||
void sendExternalDragAndDropEnter()
|
||||
{
|
||||
XClientMessageEvent msg;
|
||||
zerostruct (msg);
|
||||
|
||||
msg.message_type = getAtoms().XdndEnter;
|
||||
msg.data.l[1] = (xdndVersion << 24);
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
msg.data.l[i + 2] = (long) allowedTypes[i];
|
||||
|
||||
sendExternalDragAndDropMessage (msg);
|
||||
}
|
||||
|
||||
void sendExternalDragAndDropPosition()
|
||||
{
|
||||
XClientMessageEvent msg;
|
||||
zerostruct (msg);
|
||||
|
||||
const auto& atoms = getAtoms();
|
||||
|
||||
msg.message_type = atoms.XdndPosition;
|
||||
|
||||
auto mousePos = Desktop::getInstance().getMousePosition();
|
||||
|
||||
if (silentRect.contains (mousePos)) // we've been asked to keep silent
|
||||
return;
|
||||
|
||||
mousePos = Desktop::getInstance().getDisplays().logicalToPhysical (mousePos);
|
||||
|
||||
msg.data.l[1] = 0;
|
||||
msg.data.l[2] = (mousePos.x << 16) | mousePos.y;
|
||||
msg.data.l[3] = CurrentTime;
|
||||
msg.data.l[4] = (long) atoms.XdndActionCopy; // this is all JUCE currently supports
|
||||
|
||||
expectingStatus = sendExternalDragAndDropMessage (msg);
|
||||
}
|
||||
|
||||
void sendDragAndDropStatus (bool acceptDrop, Atom dropAction)
|
||||
{
|
||||
XClientMessageEvent msg;
|
||||
zerostruct (msg);
|
||||
|
||||
msg.message_type = getAtoms().XdndStatus;
|
||||
msg.data.l[1] = (acceptDrop ? 1 : 0) | 2; // 2 indicates that we want to receive position messages
|
||||
msg.data.l[4] = (long) dropAction;
|
||||
|
||||
sendDragAndDropMessage (msg);
|
||||
}
|
||||
|
||||
void sendExternalDragAndDropLeave()
|
||||
{
|
||||
XClientMessageEvent msg;
|
||||
zerostruct (msg);
|
||||
|
||||
msg.message_type = getAtoms().XdndLeave;
|
||||
sendExternalDragAndDropMessage (msg);
|
||||
}
|
||||
|
||||
void sendDragAndDropFinish()
|
||||
{
|
||||
XClientMessageEvent msg;
|
||||
zerostruct (msg);
|
||||
|
||||
msg.message_type = getAtoms().XdndFinished;
|
||||
sendDragAndDropMessage (msg);
|
||||
}
|
||||
|
||||
void updateDraggedFileList (const XClientMessageEvent& clientMsg, ::Window requestor)
|
||||
{
|
||||
jassert (dragInfo.isEmpty());
|
||||
|
||||
if (dragAndDropSourceWindow != None && dragAndDropCurrentMimeType != None)
|
||||
{
|
||||
auto* display = getDisplay();
|
||||
|
||||
XWindowSystemUtilities::ScopedXLock xLock;
|
||||
X11Symbols::getInstance()->xConvertSelection (display, getAtoms().XdndSelection, dragAndDropCurrentMimeType,
|
||||
XWindowSystemUtilities::Atoms::getCreating (display, "JXSelectionWindowProperty"),
|
||||
requestor, (::Time) clientMsg.data.l[2]);
|
||||
}
|
||||
}
|
||||
|
||||
bool isWindowDnDAware (::Window w) const
|
||||
{
|
||||
int numProperties = 0;
|
||||
auto* properties = X11Symbols::getInstance()->xListProperties (getDisplay(), w, &numProperties);
|
||||
|
||||
bool dndAwarePropFound = false;
|
||||
|
||||
for (int i = 0; i < numProperties; ++i)
|
||||
if (properties[i] == getAtoms().XdndAware)
|
||||
dndAwarePropFound = true;
|
||||
|
||||
if (properties != nullptr)
|
||||
X11Symbols::getInstance()->xFree (properties);
|
||||
|
||||
return dndAwarePropFound;
|
||||
}
|
||||
|
||||
int getDnDVersionForWindow (::Window target)
|
||||
{
|
||||
XWindowSystemUtilities::GetXProperty prop (getDisplay(),
|
||||
target,
|
||||
getAtoms().XdndAware,
|
||||
0,
|
||||
2,
|
||||
false,
|
||||
AnyPropertyType);
|
||||
|
||||
if (prop.success && prop.data != nullptr && prop.actualFormat == 32 && prop.numItems == 1)
|
||||
return jmin ((int) prop.data[0], (int) XWindowSystemUtilities::Atoms::DndVersion);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
::Window externalFindDragTargetWindow (::Window target)
|
||||
{
|
||||
if (target == None)
|
||||
return None;
|
||||
|
||||
if (isWindowDnDAware (target))
|
||||
return target;
|
||||
|
||||
::Window child, phonyWin;
|
||||
int phony;
|
||||
unsigned int uphony;
|
||||
|
||||
X11Symbols::getInstance()->xQueryPointer (getDisplay(), target, &phonyWin, &child, &phony, &phony, &phony, &phony, &uphony);
|
||||
|
||||
return externalFindDragTargetWindow (child);
|
||||
}
|
||||
|
||||
void handleDragAndDropDataReceived()
|
||||
{
|
||||
ComponentPeer::DragInfo dragInfoCopy (dragInfo);
|
||||
|
||||
sendDragAndDropFinish();
|
||||
resetDragAndDrop();
|
||||
|
||||
if (! dragInfoCopy.isEmpty())
|
||||
if (auto* peer = getPeerFor (windowH))
|
||||
peer->handleDragDrop (dragInfoCopy);
|
||||
}
|
||||
|
||||
void resetDragAndDrop()
|
||||
{
|
||||
dragInfo.clear();
|
||||
dragInfo.position = Point<int> (-1, -1);
|
||||
dragAndDropCurrentMimeType = 0;
|
||||
dragAndDropSourceWindow = 0;
|
||||
srcMimeTypeAtomList.clear();
|
||||
finishAfterDropDataReceived = false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
::Window windowH = 0, targetWindow = 0, dragAndDropSourceWindow = 0;
|
||||
|
||||
int xdndVersion = -1;
|
||||
bool isText = false, dragging = false, expectingStatus = false, canDrop = false, finishAfterDropDataReceived = false;
|
||||
|
||||
Atom dragAndDropCurrentMimeType;
|
||||
Array<Atom> allowedTypes, srcMimeTypeAtomList;
|
||||
|
||||
ComponentPeer::DragInfo dragInfo;
|
||||
Rectangle<int> silentRect;
|
||||
String textOrFiles;
|
||||
|
||||
std::function<void()> completionCallback = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_LEAK_DETECTOR (X11DragState)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+368
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace DragAndDropHelpers
|
||||
{
|
||||
//==============================================================================
|
||||
struct JuceDropSource final : public ComBaseClassHelper<IDropSource>
|
||||
{
|
||||
JuceDropSource() = default;
|
||||
|
||||
JUCE_COMRESULT QueryContinueDrag (BOOL escapePressed, DWORD keys) override
|
||||
{
|
||||
if (escapePressed)
|
||||
return DRAGDROP_S_CANCEL;
|
||||
|
||||
if ((keys & (MK_LBUTTON | MK_RBUTTON)) == 0)
|
||||
return DRAGDROP_S_DROP;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GiveFeedback (DWORD) override
|
||||
{
|
||||
return DRAGDROP_S_USEDEFAULTCURSORS;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct JuceEnumFormatEtc final : public ComBaseClassHelper<IEnumFORMATETC>
|
||||
{
|
||||
JuceEnumFormatEtc (const FORMATETC* f) : format (f) {}
|
||||
|
||||
JUCE_COMRESULT Clone (IEnumFORMATETC** result) override
|
||||
{
|
||||
if (result == nullptr)
|
||||
return E_POINTER;
|
||||
|
||||
auto newOne = new JuceEnumFormatEtc (format);
|
||||
newOne->index = index;
|
||||
*result = newOne;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Next (ULONG celt, LPFORMATETC lpFormatEtc, ULONG* pceltFetched) override
|
||||
{
|
||||
if (pceltFetched != nullptr)
|
||||
*pceltFetched = 0;
|
||||
else if (celt != 1)
|
||||
return S_FALSE;
|
||||
|
||||
if (index == 0 && celt > 0 && lpFormatEtc != nullptr)
|
||||
{
|
||||
copyFormatEtc (lpFormatEtc [0], *format);
|
||||
++index;
|
||||
|
||||
if (pceltFetched != nullptr)
|
||||
*pceltFetched = 1;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Skip (ULONG celt) override
|
||||
{
|
||||
if (index + (int) celt >= 1)
|
||||
return S_FALSE;
|
||||
|
||||
index += (int) celt;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Reset() override
|
||||
{
|
||||
index = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
const FORMATETC* const format;
|
||||
int index = 0;
|
||||
|
||||
static void copyFormatEtc (FORMATETC& dest, const FORMATETC& source)
|
||||
{
|
||||
dest = source;
|
||||
|
||||
if (source.ptd != nullptr)
|
||||
{
|
||||
dest.ptd = (DVTARGETDEVICE*) CoTaskMemAlloc (sizeof (DVTARGETDEVICE));
|
||||
|
||||
if (dest.ptd != nullptr)
|
||||
*(dest.ptd) = *(source.ptd);
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (JuceEnumFormatEtc)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class JuceDataObject final : public ComBaseClassHelper<IDataObject>
|
||||
{
|
||||
public:
|
||||
JuceDataObject (const FORMATETC* f, const STGMEDIUM* m)
|
||||
: format (f), medium (m)
|
||||
{
|
||||
}
|
||||
|
||||
~JuceDataObject() override
|
||||
{
|
||||
jassert (refCount == 0);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetData (FORMATETC* pFormatEtc, STGMEDIUM* pMedium) override
|
||||
{
|
||||
if ((pFormatEtc->tymed & format->tymed) != 0
|
||||
&& pFormatEtc->cfFormat == format->cfFormat
|
||||
&& pFormatEtc->dwAspect == format->dwAspect)
|
||||
{
|
||||
pMedium->tymed = format->tymed;
|
||||
pMedium->pUnkForRelease = nullptr;
|
||||
|
||||
if (format->tymed == TYMED_HGLOBAL)
|
||||
{
|
||||
auto len = GlobalSize (medium->hGlobal);
|
||||
void* const src = GlobalLock (medium->hGlobal);
|
||||
void* const dst = GlobalAlloc (GMEM_FIXED, len);
|
||||
|
||||
if (src != nullptr && dst != nullptr)
|
||||
memcpy (dst, src, len);
|
||||
|
||||
GlobalUnlock (medium->hGlobal);
|
||||
|
||||
pMedium->hGlobal = dst;
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return DV_E_FORMATETC;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT QueryGetData (FORMATETC* f) override
|
||||
{
|
||||
if (f == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
if (f->tymed == format->tymed
|
||||
&& f->cfFormat == format->cfFormat
|
||||
&& f->dwAspect == format->dwAspect)
|
||||
return S_OK;
|
||||
|
||||
return DV_E_FORMATETC;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetCanonicalFormatEtc (FORMATETC*, FORMATETC* pFormatEtcOut) override
|
||||
{
|
||||
pFormatEtcOut->ptd = nullptr;
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT EnumFormatEtc (DWORD direction, IEnumFORMATETC** result) override
|
||||
{
|
||||
if (result == nullptr)
|
||||
return E_POINTER;
|
||||
|
||||
if (direction == DATADIR_GET)
|
||||
{
|
||||
*result = new JuceEnumFormatEtc (format);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
*result = nullptr;
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetDataHere (FORMATETC*, STGMEDIUM*) override { return DATA_E_FORMATETC; }
|
||||
JUCE_COMRESULT SetData (FORMATETC*, STGMEDIUM*, BOOL) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT DAdvise (FORMATETC*, DWORD, IAdviseSink*, DWORD*) override { return OLE_E_ADVISENOTSUPPORTED; }
|
||||
JUCE_COMRESULT DUnadvise (DWORD) override { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT EnumDAdvise (IEnumSTATDATA**) override { return OLE_E_ADVISENOTSUPPORTED; }
|
||||
|
||||
private:
|
||||
const FORMATETC* const format;
|
||||
const STGMEDIUM* const medium;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (JuceDataObject)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static HDROP createHDrop (const StringArray& fileNames)
|
||||
{
|
||||
size_t totalBytes = 0;
|
||||
for (int i = fileNames.size(); --i >= 0;)
|
||||
totalBytes += CharPointer_UTF16::getBytesRequiredFor (fileNames[i].getCharPointer()) + sizeof (WCHAR);
|
||||
|
||||
struct Deleter
|
||||
{
|
||||
void operator() (void* ptr) const noexcept { GlobalFree (ptr); }
|
||||
};
|
||||
|
||||
auto hDrop = std::unique_ptr<void, Deleter> ((HDROP) GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof (DROPFILES) + totalBytes + 4));
|
||||
|
||||
if (hDrop != nullptr)
|
||||
{
|
||||
auto pDropFiles = (LPDROPFILES) GlobalLock (hDrop.get());
|
||||
|
||||
if (pDropFiles == nullptr)
|
||||
return nullptr;
|
||||
|
||||
pDropFiles->pFiles = sizeof (DROPFILES);
|
||||
pDropFiles->fWide = true;
|
||||
|
||||
auto* fname = reinterpret_cast<WCHAR*> (addBytesToPointer (pDropFiles, sizeof (DROPFILES)));
|
||||
|
||||
for (int i = 0; i < fileNames.size(); ++i)
|
||||
{
|
||||
auto bytesWritten = fileNames[i].copyToUTF16 (fname, 2048);
|
||||
fname = reinterpret_cast<WCHAR*> (addBytesToPointer (fname, bytesWritten));
|
||||
}
|
||||
|
||||
*fname = 0;
|
||||
|
||||
GlobalUnlock (hDrop.get());
|
||||
}
|
||||
|
||||
return static_cast<HDROP> (hDrop.release());
|
||||
}
|
||||
|
||||
struct DragAndDropJob final : public ThreadPoolJob
|
||||
{
|
||||
DragAndDropJob (FORMATETC f, STGMEDIUM m, DWORD d, std::function<void()>&& cb)
|
||||
: ThreadPoolJob ("DragAndDrop"),
|
||||
format (f), medium (m), whatToDo (d),
|
||||
completionCallback (std::move (cb))
|
||||
{
|
||||
}
|
||||
|
||||
JobStatus runJob() override
|
||||
{
|
||||
[[maybe_unused]] const auto result = OleInitialize (nullptr);
|
||||
|
||||
auto* source = new JuceDropSource();
|
||||
auto* data = new JuceDataObject (&format, &medium);
|
||||
|
||||
DWORD effect;
|
||||
DoDragDrop (data, source, whatToDo, &effect);
|
||||
|
||||
data->Release();
|
||||
source->Release();
|
||||
|
||||
OleUninitialize();
|
||||
|
||||
if (completionCallback != nullptr)
|
||||
MessageManager::callAsync (std::move (completionCallback));
|
||||
|
||||
return jobHasFinished;
|
||||
}
|
||||
|
||||
FORMATETC format;
|
||||
STGMEDIUM medium;
|
||||
DWORD whatToDo;
|
||||
|
||||
std::function<void()> completionCallback;
|
||||
};
|
||||
|
||||
class ThreadPoolHolder final : private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
ThreadPoolHolder() = default;
|
||||
|
||||
~ThreadPoolHolder()
|
||||
{
|
||||
// Wait forever if there's a job running. The user needs to cancel the transfer
|
||||
// in the GUI.
|
||||
pool.removeAllJobs (true, -1);
|
||||
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED (ThreadPoolHolder, false)
|
||||
|
||||
// We need to make sure we don't do simultaneous text and file drag and drops,
|
||||
// so use a pool that can only run a single job.
|
||||
ThreadPool pool { ThreadPoolOptions{}.withNumberOfThreads (1) };
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (ThreadPoolHolder)
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMove,
|
||||
Component*, std::function<void()> callback)
|
||||
{
|
||||
if (files.isEmpty())
|
||||
return false;
|
||||
|
||||
FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
||||
STGMEDIUM medium = { TYMED_HGLOBAL, { nullptr }, nullptr };
|
||||
|
||||
medium.hGlobal = DragAndDropHelpers::createHDrop (files);
|
||||
|
||||
auto& pool = DragAndDropHelpers::ThreadPoolHolder::getInstance()->pool;
|
||||
pool.addJob (new DragAndDropHelpers::DragAndDropJob (format, medium,
|
||||
canMove ? (DROPEFFECT_COPY | DROPEFFECT_MOVE) : DROPEFFECT_COPY,
|
||||
std::move (callback)),
|
||||
true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component*, std::function<void()> callback)
|
||||
{
|
||||
if (text.isEmpty())
|
||||
return false;
|
||||
|
||||
FORMATETC format = { CF_TEXT, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
|
||||
STGMEDIUM medium = { TYMED_HGLOBAL, { nullptr }, nullptr };
|
||||
|
||||
auto numBytes = CharPointer_UTF16::getBytesRequiredFor (text.getCharPointer());
|
||||
|
||||
medium.hGlobal = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT, numBytes + 2);
|
||||
|
||||
if (medium.hGlobal == nullptr)
|
||||
return false;
|
||||
|
||||
auto* data = static_cast<WCHAR*> (GlobalLock (medium.hGlobal));
|
||||
|
||||
text.copyToUTF16 (data, numBytes + 2);
|
||||
format.cfFormat = CF_UNICODETEXT;
|
||||
|
||||
GlobalUnlock (medium.hGlobal);
|
||||
|
||||
auto& pool = DragAndDropHelpers::ThreadPoolHolder::getInstance()->pool;
|
||||
pool.addJob (new DragAndDropHelpers::DragAndDropJob (format,
|
||||
medium,
|
||||
DROPEFFECT_COPY | DROPEFFECT_MOVE,
|
||||
std::move (callback)),
|
||||
true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getItemCount, "getItemCount", "()I") \
|
||||
METHOD (getItemAt, "getItemAt", "(I)Landroid/content/ClipData$Item;")
|
||||
|
||||
DECLARE_JNI_CLASS (ClipData, "android/content/ClipData")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getUri, "getUri", "()Landroid/net/Uri;")
|
||||
|
||||
DECLARE_JNI_CLASS (ClipDataItem, "android/content/ClipData$Item")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
class FileChooser::Native final : public FileChooser::Pimpl
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
Native (FileChooser& fileChooser, int flags) : owner (fileChooser)
|
||||
{
|
||||
if (currentFileChooser == nullptr)
|
||||
{
|
||||
currentFileChooser = this;
|
||||
auto* env = getEnv();
|
||||
|
||||
auto sdkVersion = getAndroidSDKVersion();
|
||||
auto saveMode = ((flags & FileBrowserComponent::saveMode) != 0);
|
||||
auto selectsDirectories = ((flags & FileBrowserComponent::canSelectDirectories) != 0);
|
||||
auto canSelectMultiple = ((flags & FileBrowserComponent::canSelectMultipleItems) != 0);
|
||||
|
||||
// You cannot save a directory
|
||||
jassert (! (saveMode && selectsDirectories));
|
||||
|
||||
if (sdkVersion < 19)
|
||||
{
|
||||
// native save dialogs are only supported in Android versions >= 19
|
||||
jassert (! saveMode);
|
||||
saveMode = false;
|
||||
}
|
||||
|
||||
if (sdkVersion < 21)
|
||||
{
|
||||
// native directory chooser dialogs are only supported in Android versions >= 21
|
||||
jassert (! selectsDirectories);
|
||||
selectsDirectories = false;
|
||||
}
|
||||
|
||||
const char* action = (selectsDirectories ? "android.intent.action.OPEN_DOCUMENT_TREE"
|
||||
: (saveMode ? "android.intent.action.CREATE_DOCUMENT"
|
||||
: (sdkVersion >= 19 ? "android.intent.action.OPEN_DOCUMENT"
|
||||
: "android.intent.action.GET_CONTENT")));
|
||||
|
||||
|
||||
intent = GlobalRef (LocalRef<jobject> (env->NewObject (AndroidIntent, AndroidIntent.constructWithString,
|
||||
javaString (action).get())));
|
||||
|
||||
if (owner.startingFile != File())
|
||||
{
|
||||
if (saveMode && (! owner.startingFile.isDirectory()))
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.putExtraString,
|
||||
javaString ("android.intent.extra.TITLE").get(),
|
||||
javaString (owner.startingFile.getFileName()).get());
|
||||
|
||||
|
||||
URL url (owner.startingFile);
|
||||
LocalRef<jobject> uri (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse,
|
||||
javaString (url.toString (true)).get()));
|
||||
|
||||
if (uri)
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.putExtraParcelable,
|
||||
javaString ("android.provider.extra.INITIAL_URI").get(),
|
||||
uri.get());
|
||||
}
|
||||
|
||||
if (canSelectMultiple && sdkVersion >= 18)
|
||||
{
|
||||
env->CallObjectMethod (intent.get(),
|
||||
AndroidIntent.putExtraBool,
|
||||
javaString ("android.intent.extra.ALLOW_MULTIPLE").get(),
|
||||
true);
|
||||
}
|
||||
|
||||
if (! selectsDirectories)
|
||||
{
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.addCategory,
|
||||
javaString ("android.intent.category.OPENABLE").get());
|
||||
|
||||
auto mimeTypes = convertFiltersToMimeTypes (owner.filters);
|
||||
|
||||
if (mimeTypes.size() == 1)
|
||||
{
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.setType, javaString (mimeTypes[0]).get());
|
||||
}
|
||||
else
|
||||
{
|
||||
String mimeGroup = "*";
|
||||
|
||||
if (mimeTypes.size() > 0)
|
||||
{
|
||||
mimeGroup = mimeTypes[0].upToFirstOccurrenceOf ("/", false, false);
|
||||
auto allMimeTypesHaveSameGroup = true;
|
||||
|
||||
LocalRef<jobjectArray> jMimeTypes (env->NewObjectArray (mimeTypes.size(), JavaString,
|
||||
javaString ("").get()));
|
||||
|
||||
for (int i = 0; i < mimeTypes.size(); ++i)
|
||||
{
|
||||
env->SetObjectArrayElement (jMimeTypes.get(), i, javaString (mimeTypes[i]).get());
|
||||
|
||||
if (mimeGroup != mimeTypes[i].upToFirstOccurrenceOf ("/", false, false))
|
||||
allMimeTypesHaveSameGroup = false;
|
||||
}
|
||||
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.putExtraStrings,
|
||||
javaString ("android.intent.extra.MIME_TYPES").get(),
|
||||
jMimeTypes.get());
|
||||
|
||||
if (! allMimeTypesHaveSameGroup)
|
||||
mimeGroup = "*";
|
||||
}
|
||||
|
||||
env->CallObjectMethod (intent.get(), AndroidIntent.setType, javaString (mimeGroup + "/*").get());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
jassertfalse; // there can only be a single file chooser
|
||||
}
|
||||
|
||||
~Native() override
|
||||
{
|
||||
masterReference.clear();
|
||||
currentFileChooser = nullptr;
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
// Android does not support modal file choosers
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
if (currentFileChooser != nullptr)
|
||||
{
|
||||
startAndroidActivityForResult (LocalRef<jobject> (env->NewLocalRef (intent.get())), /*READ_REQUEST_CODE*/ 42,
|
||||
[myself = WeakReference<Native> { this }] (int requestCode, int resultCode, LocalRef<jobject> intentData) mutable
|
||||
{
|
||||
if (myself != nullptr)
|
||||
myself->onActivityResult (requestCode, resultCode, intentData);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse; // There is already a file chooser running
|
||||
}
|
||||
}
|
||||
|
||||
void onActivityResult (int /*requestCode*/, int resultCode, const LocalRef<jobject>& intentData)
|
||||
{
|
||||
currentFileChooser = nullptr;
|
||||
auto* env = getEnv();
|
||||
|
||||
const auto getUrls = [&]() -> Array<URL>
|
||||
{
|
||||
if (resultCode != /*Activity.RESULT_OK*/ -1 || intentData == nullptr)
|
||||
return {};
|
||||
|
||||
Array<URL> chosenURLs;
|
||||
|
||||
const auto addUrl = [env, &chosenURLs] (jobject uri)
|
||||
{
|
||||
if (auto jStr = (jstring) env->CallObjectMethod (uri, JavaObject.toString))
|
||||
chosenURLs.add (URL (juceString (env, jStr)));
|
||||
};
|
||||
|
||||
if (LocalRef<jobject> clipData { env->CallObjectMethod (intentData.get(), AndroidIntent.getClipData) })
|
||||
{
|
||||
const auto count = env->CallIntMethod (clipData.get(), ClipData.getItemCount);
|
||||
|
||||
for (auto i = 0; i < count; ++i)
|
||||
{
|
||||
if (LocalRef<jobject> item { env->CallObjectMethod (clipData.get(), ClipData.getItemAt, i) })
|
||||
{
|
||||
if (LocalRef<jobject> itemUri { env->CallObjectMethod (item.get(), ClipDataItem.getUri) })
|
||||
addUrl (itemUri.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (LocalRef<jobject> uri { env->CallObjectMethod (intentData.get(), AndroidIntent.getData )})
|
||||
{
|
||||
addUrl (uri.get());
|
||||
}
|
||||
|
||||
return chosenURLs;
|
||||
};
|
||||
|
||||
owner.finished (getUrls());
|
||||
}
|
||||
|
||||
static Native* currentFileChooser;
|
||||
|
||||
static StringArray convertFiltersToMimeTypes (const String& fileFilters)
|
||||
{
|
||||
StringArray result;
|
||||
auto wildcards = StringArray::fromTokens (fileFilters, ";", "");
|
||||
|
||||
for (auto wildcard : wildcards)
|
||||
{
|
||||
if (wildcard.upToLastOccurrenceOf (".", false, false) == "*")
|
||||
{
|
||||
auto extension = wildcard.fromLastOccurrenceOf (".", false, false);
|
||||
|
||||
result.addArray (detail::MimeTypeTable::getMimeTypesForFileExtension (extension));
|
||||
}
|
||||
}
|
||||
|
||||
result.removeDuplicates (false);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (Native)
|
||||
|
||||
FileChooser& owner;
|
||||
GlobalRef intent;
|
||||
};
|
||||
|
||||
FileChooser::Native* FileChooser::Native::currentFileChooser = nullptr;
|
||||
|
||||
std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
||||
FilePreviewComponent*)
|
||||
{
|
||||
if (FileChooser::Native::currentFileChooser == nullptr)
|
||||
return std::make_shared<FileChooser::Native> (owner, flags);
|
||||
|
||||
// there can only be one file chooser on Android at a once
|
||||
jassertfalse;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileChooser::registerCustomMimeTypeForFileExtension (const String& mimeType,
|
||||
const String& fileExtension)
|
||||
{
|
||||
detail::MimeTypeTable::registerCustomMimeTypeForFileExtension (mimeType, fileExtension);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
@interface FileChooserControllerClass : UIDocumentPickerViewController
|
||||
- (void) setParent: (FileChooser::Native*) ptr;
|
||||
@end
|
||||
|
||||
@interface FileChooserDelegateClass : NSObject<UIDocumentPickerDelegate, UIAdaptivePresentationControllerDelegate>
|
||||
- (id) initWithOwner: (FileChooser::Native*) owner;
|
||||
@end
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if ! (defined (__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0)
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
#define JUCE_DEPRECATION_IGNORED 1
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
class FileChooser::Native final : public FileChooser::Pimpl,
|
||||
public detail::NativeModalWrapperComponent,
|
||||
public std::enable_shared_from_this<Native>
|
||||
{
|
||||
public:
|
||||
static std::shared_ptr<Native> make (FileChooser& fileChooser, int flags)
|
||||
{
|
||||
std::shared_ptr<Native> result { new Native (fileChooser, flags) };
|
||||
/* Must be called after forming a shared_ptr to an instance of this class.
|
||||
Note that we can't call this directly inside the class constructor, because
|
||||
the owning shared_ptr might not yet exist.
|
||||
*/
|
||||
[result->controller.get() setParent: result.get()];
|
||||
return result;
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
jassert (shared_from_this() != nullptr);
|
||||
|
||||
/* Normally, when deleteWhenDismissed is true, the modal component manager will keep a copy of a raw pointer
|
||||
to our component and delete it when the modal state has ended. However, this is incompatible with
|
||||
our class being tracked by shared_ptr as it will force delete our class regardless of the current
|
||||
reference count. On the other hand, it's important that the modal manager keeps a reference as it can
|
||||
sometimes be the only reference to our class.
|
||||
|
||||
To do this, we set deleteWhenDismissed to false so that the modal component manager does not delete
|
||||
our class. Instead, we pass in a lambda which captures a shared_ptr to ourselves to increase the
|
||||
reference count while the component is modal.
|
||||
*/
|
||||
enterModalState (true,
|
||||
ModalCallbackFunction::create ([_self = shared_from_this()] (int) {}),
|
||||
false);
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
launch();
|
||||
runModalLoop();
|
||||
#else
|
||||
jassertfalse;
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void didPickDocumentsAtURLs (NSArray<NSURL*>* urls)
|
||||
{
|
||||
const auto isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService
|
||||
|| controller.get().documentPickerMode == UIDocumentPickerModeMoveToService;
|
||||
const auto accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges;
|
||||
|
||||
auto* fileCoordinator = [[[NSFileCoordinator alloc] initWithFilePresenter: nil] autorelease];
|
||||
auto* intents = [[[NSMutableArray alloc] init] autorelease];
|
||||
|
||||
for (NSURL* url in urls)
|
||||
{
|
||||
auto* fileAccessIntent = isWriting
|
||||
? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions]
|
||||
: [NSFileAccessIntent readingIntentWithURL: url options: accessOptions];
|
||||
[intents addObject: fileAccessIntent];
|
||||
}
|
||||
|
||||
[fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err)
|
||||
{
|
||||
if (err != nil)
|
||||
{
|
||||
[[maybe_unused]] auto desc = [err localizedDescription];
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
Array<URL> result;
|
||||
|
||||
for (NSURL* url in urls)
|
||||
{
|
||||
[url startAccessingSecurityScopedResource];
|
||||
|
||||
NSError* error = nil;
|
||||
|
||||
auto* bookmark = [url bookmarkDataWithOptions: 0
|
||||
includingResourceValuesForKeys: nil
|
||||
relativeToURL: nil
|
||||
error: &error];
|
||||
|
||||
[bookmark retain];
|
||||
|
||||
[url stopAccessingSecurityScopedResource];
|
||||
|
||||
URL juceUrl (nsStringToJuce ([url absoluteString]));
|
||||
|
||||
if (error == nil)
|
||||
{
|
||||
setURLBookmark (juceUrl, (void*) bookmark);
|
||||
}
|
||||
else
|
||||
{
|
||||
[[maybe_unused]] auto desc = [error localizedDescription];
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
result.add (std::move (juceUrl));
|
||||
}
|
||||
|
||||
passResultsToInitiator (std::move (result));
|
||||
}];
|
||||
}
|
||||
|
||||
void didPickDocumentAtURL (NSURL* url)
|
||||
{
|
||||
didPickDocumentsAtURLs (@[url]);
|
||||
}
|
||||
|
||||
void pickerWasCancelled()
|
||||
{
|
||||
passResultsToInitiator ({});
|
||||
}
|
||||
|
||||
private:
|
||||
UIViewController* getViewController() const override { return controller.get(); }
|
||||
|
||||
Native (FileChooser& fileChooser, int flags)
|
||||
: owner (fileChooser)
|
||||
{
|
||||
delegate.reset ([[FileChooserDelegateClass alloc] initWithOwner: this]);
|
||||
|
||||
const auto validExtensions = getValidExtensionsForWildcards (owner.filters);
|
||||
const auto utTypeArray = (flags & FileBrowserComponent::canSelectDirectories) != 0
|
||||
? @[@"public.folder"]
|
||||
: createNSArrayFromStringArray (getUTTypesForExtensions (validExtensions));
|
||||
|
||||
if ((flags & FileBrowserComponent::saveMode) != 0)
|
||||
{
|
||||
auto currentFileOrDirectory = owner.startingFile;
|
||||
|
||||
UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile()
|
||||
? UIDocumentPickerModeExportToService
|
||||
: UIDocumentPickerModeMoveToService;
|
||||
|
||||
if (! currentFileOrDirectory.existsAsFile())
|
||||
{
|
||||
const auto extension = validExtensions.isEmpty() ? String()
|
||||
: validExtensions.getReference (0);
|
||||
const auto filename = getFilename (currentFileOrDirectory, extension);
|
||||
const auto tmpDirectory = File::createTempFile ("JUCE-filepath");
|
||||
|
||||
if (tmpDirectory.createDirectory().wasOk())
|
||||
{
|
||||
currentFileOrDirectory = tmpDirectory.getChildFile (filename);
|
||||
currentFileOrDirectory.replaceWithText ("");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Temporary directory creation failed! You need to specify a
|
||||
// path you have write access to. Saving will not work for
|
||||
// current path.
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
|
||||
auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())];
|
||||
|
||||
controller.reset ([[FileChooserControllerClass alloc] initWithURL: url inMode: pickerMode]);
|
||||
[url release];
|
||||
}
|
||||
else
|
||||
{
|
||||
controller.reset ([[FileChooserControllerClass alloc] initWithDocumentTypes: utTypeArray inMode: UIDocumentPickerModeOpen]);
|
||||
|
||||
if (@available (iOS 11.0, *))
|
||||
[controller.get() setAllowsMultipleSelection: (flags & FileBrowserComponent::canSelectMultipleItems) != 0];
|
||||
}
|
||||
|
||||
[controller.get() setDelegate: delegate.get()];
|
||||
|
||||
if (auto* pc = [controller.get() presentationController])
|
||||
[pc setDelegate: delegate.get()];
|
||||
|
||||
displayNativeWindowModally (fileChooser.parent);
|
||||
}
|
||||
|
||||
void passResultsToInitiator (Array<URL> urls)
|
||||
{
|
||||
exitModalState (0);
|
||||
|
||||
// If the caller attempts to show a platform-native dialog box inside the results callback (e.g. in the DialogsDemo)
|
||||
// then the original peer must already have focus. Otherwise, there's a danger that either the invisible FileChooser
|
||||
// components will display the popup, locking the application, or maybe no component will have focus, and the
|
||||
// dialog won't show at all.
|
||||
for (auto i = 0; i < ComponentPeer::getNumPeers(); ++i)
|
||||
if (auto* p = ComponentPeer::getPeer (i))
|
||||
if (p != getPeer())
|
||||
if (auto* view = (UIView*) p->getNativeHandle())
|
||||
if ([view becomeFirstResponder] && [view isFirstResponder])
|
||||
break;
|
||||
|
||||
// Calling owner.finished will delete this Pimpl instance, so don't call any more member functions here!
|
||||
owner.finished (std::move (urls));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static StringArray getValidExtensionsForWildcards (const String& filterWildcards)
|
||||
{
|
||||
const auto filters = StringArray::fromTokens (filterWildcards, ";", "");
|
||||
|
||||
if (filters.contains ("*") || filters.isEmpty())
|
||||
return {};
|
||||
|
||||
StringArray result;
|
||||
|
||||
for (const auto& filter : filters)
|
||||
{
|
||||
if (filter.isEmpty())
|
||||
continue;
|
||||
|
||||
// iOS only supports file extension wild cards
|
||||
jassert (filter.upToLastOccurrenceOf (".", true, false) == "*.");
|
||||
|
||||
result.add (filter.fromLastOccurrenceOf (".", false, false));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static StringArray getUTTypesForExtensions (const StringArray& extensions)
|
||||
{
|
||||
if (extensions.isEmpty())
|
||||
return { "public.data" };
|
||||
|
||||
StringArray result;
|
||||
|
||||
for (const auto& extension : extensions)
|
||||
{
|
||||
if (extension.isEmpty())
|
||||
continue;
|
||||
|
||||
CFUniquePtr<CFStringRef> fileExtensionCF (extension.toCFString());
|
||||
|
||||
if (const auto tag = CFUniquePtr<CFStringRef> (UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF.get(), nullptr)))
|
||||
result.add (String::fromCFString (tag.get()));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static String getFilename (const File& path, const String& fallbackExtension)
|
||||
{
|
||||
auto filename = path.getFileNameWithoutExtension();
|
||||
auto extension = path.getFileExtension().substring (1);
|
||||
|
||||
if (filename.isEmpty())
|
||||
filename = "Untitled";
|
||||
|
||||
if (extension.isEmpty())
|
||||
extension = fallbackExtension;
|
||||
|
||||
if (extension.isNotEmpty())
|
||||
filename += "." + extension;
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FileChooser& owner;
|
||||
NSUniquePtr<NSObject<UIDocumentPickerDelegate, UIAdaptivePresentationControllerDelegate>> delegate;
|
||||
NSUniquePtr<FileChooserControllerClass> controller;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
||||
FilePreviewComponent*)
|
||||
{
|
||||
return Native::make (owner, flags);
|
||||
}
|
||||
|
||||
#if JUCE_DEPRECATION_IGNORED
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
|
||||
@implementation FileChooserControllerClass
|
||||
{
|
||||
std::weak_ptr<FileChooser::Native> ptr;
|
||||
}
|
||||
|
||||
- (void) setParent: (FileChooser::Native*) parent
|
||||
{
|
||||
jassert (parent != nullptr);
|
||||
jassert (parent->shared_from_this() != nullptr);
|
||||
ptr = parent->weak_from_this();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation FileChooserDelegateClass
|
||||
{
|
||||
FileChooser::Native* owner;
|
||||
}
|
||||
|
||||
- (id) initWithOwner: (FileChooser::Native*) o
|
||||
{
|
||||
self = [super init];
|
||||
owner = o;
|
||||
return self;
|
||||
}
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-implementations")
|
||||
- (void) documentPicker: (UIDocumentPickerViewController*) controller didPickDocumentAtURL: (NSURL*) url
|
||||
{
|
||||
if (owner != nullptr)
|
||||
owner->didPickDocumentAtURL (url);
|
||||
}
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
- (void) documentPicker: (UIDocumentPickerViewController*) controller didPickDocumentsAtURLs: (NSArray<NSURL*>*) urls
|
||||
{
|
||||
if (owner != nullptr)
|
||||
owner->didPickDocumentsAtURLs (urls);
|
||||
}
|
||||
|
||||
- (void) documentPickerWasCancelled: (UIDocumentPickerViewController*) controller
|
||||
{
|
||||
if (owner != nullptr)
|
||||
owner->pickerWasCancelled();
|
||||
}
|
||||
|
||||
- (void) presentationControllerDidDismiss: (UIPresentationController *) presentationController
|
||||
{
|
||||
if (owner != nullptr)
|
||||
owner->pickerWasCancelled();
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static bool exeIsAvailable (String executable)
|
||||
{
|
||||
ChildProcess child;
|
||||
|
||||
if (child.start ("which " + executable))
|
||||
{
|
||||
child.waitForProcessToFinish (60 * 1000);
|
||||
return (child.getExitCode() == 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isSet (int flags, int toCheck)
|
||||
{
|
||||
return (flags & toCheck) != 0;
|
||||
}
|
||||
|
||||
class FileChooser::Native final : public FileChooser::Pimpl,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
Native (FileChooser& fileChooser, int flags)
|
||||
: owner (fileChooser),
|
||||
// kdialog/zenity only support opening either files or directories.
|
||||
// Files should take precedence, if requested.
|
||||
isDirectory (isSet (flags, FileBrowserComponent::canSelectDirectories) && ! isSet (flags, FileBrowserComponent::canSelectFiles)),
|
||||
isSave (isSet (flags, FileBrowserComponent::saveMode)),
|
||||
selectMultipleFiles (isSet (flags, FileBrowserComponent::canSelectMultipleItems)),
|
||||
warnAboutOverwrite (isSet (flags, FileBrowserComponent::warnAboutOverwriting))
|
||||
{
|
||||
const File previousWorkingDirectory (File::getCurrentWorkingDirectory());
|
||||
|
||||
// use kdialog for KDE sessions or if zenity is missing
|
||||
if (exeIsAvailable ("kdialog") && (isKdeFullSession() || ! exeIsAvailable ("zenity")))
|
||||
addKDialogArgs();
|
||||
else
|
||||
addZenityArgs();
|
||||
}
|
||||
|
||||
~Native() override
|
||||
{
|
||||
finish (true);
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
child.start (args, ChildProcess::wantStdOut);
|
||||
|
||||
while (child.isRunning())
|
||||
if (! MessageManager::getInstance()->runDispatchLoopUntil (20))
|
||||
break;
|
||||
|
||||
finish (false);
|
||||
#else
|
||||
jassertfalse;
|
||||
#endif
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
child.start (args, ChildProcess::wantStdOut);
|
||||
startTimer (100);
|
||||
}
|
||||
|
||||
private:
|
||||
FileChooser& owner;
|
||||
bool isDirectory, isSave, selectMultipleFiles, warnAboutOverwrite;
|
||||
|
||||
ChildProcess child;
|
||||
StringArray args;
|
||||
String separator;
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (! child.isRunning())
|
||||
{
|
||||
stopTimer();
|
||||
finish (false);
|
||||
}
|
||||
}
|
||||
|
||||
void finish (bool shouldKill)
|
||||
{
|
||||
String result;
|
||||
Array<URL> selection;
|
||||
|
||||
if (shouldKill)
|
||||
child.kill();
|
||||
else
|
||||
result = child.readAllProcessOutput().trim();
|
||||
|
||||
if (result.isNotEmpty())
|
||||
{
|
||||
StringArray tokens;
|
||||
|
||||
if (selectMultipleFiles)
|
||||
tokens.addTokens (result, separator, "\"");
|
||||
else
|
||||
tokens.add (result);
|
||||
|
||||
for (auto& token : tokens)
|
||||
selection.add (URL (File::getCurrentWorkingDirectory().getChildFile (token)));
|
||||
}
|
||||
|
||||
if (! shouldKill)
|
||||
{
|
||||
child.waitForProcessToFinish (60 * 1000);
|
||||
owner.finished (selection);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64 getTopWindowID() noexcept
|
||||
{
|
||||
if (TopLevelWindow* top = TopLevelWindow::getActiveTopLevelWindow())
|
||||
return (uint64) (pointer_sized_uint) top->getWindowHandle();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool isKdeFullSession()
|
||||
{
|
||||
return SystemStats::getEnvironmentVariable ("KDE_FULL_SESSION", String())
|
||||
.equalsIgnoreCase ("true");
|
||||
}
|
||||
|
||||
void addKDialogArgs()
|
||||
{
|
||||
args.add ("kdialog");
|
||||
|
||||
if (owner.title.isNotEmpty())
|
||||
args.add ("--title=" + owner.title);
|
||||
|
||||
if (uint64 topWindowID = getTopWindowID())
|
||||
{
|
||||
args.add ("--attach");
|
||||
args.add (String (topWindowID));
|
||||
}
|
||||
|
||||
if (selectMultipleFiles)
|
||||
{
|
||||
separator = "\n";
|
||||
args.add ("--multiple");
|
||||
args.add ("--separate-output");
|
||||
args.add ("--getopenfilename");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isSave) args.add ("--getsavefilename");
|
||||
else if (isDirectory) args.add ("--getexistingdirectory");
|
||||
else args.add ("--getopenfilename");
|
||||
}
|
||||
|
||||
File startPath;
|
||||
|
||||
if (owner.startingFile.exists())
|
||||
{
|
||||
startPath = owner.startingFile;
|
||||
}
|
||||
else if (owner.startingFile.getParentDirectory().exists())
|
||||
{
|
||||
startPath = owner.startingFile.getParentDirectory();
|
||||
}
|
||||
else
|
||||
{
|
||||
startPath = File::getSpecialLocation (File::userHomeDirectory);
|
||||
|
||||
if (isSave)
|
||||
startPath = startPath.getChildFile (owner.startingFile.getFileName());
|
||||
}
|
||||
|
||||
args.add (startPath.getFullPathName());
|
||||
args.add ("(" + owner.filters.replaceCharacter (';', ' ') + ")");
|
||||
}
|
||||
|
||||
void addZenityArgs()
|
||||
{
|
||||
args.add ("zenity");
|
||||
args.add ("--file-selection");
|
||||
|
||||
const auto getUnderstandsConfirmOverwrite = []
|
||||
{
|
||||
// --confirm-overwrite is deprecated in zenity 3.91 and higher
|
||||
ChildProcess process;
|
||||
process.start ("zenity --version");
|
||||
process.waitForProcessToFinish (1000);
|
||||
const auto versionString = process.readAllProcessOutput();
|
||||
const auto version = StringArray::fromTokens (versionString.trim(), ".", "");
|
||||
return version.size() >= 2
|
||||
&& (version[0].getIntValue() < 3
|
||||
|| (version[0].getIntValue() == 3 && version[1].getIntValue() < 91));
|
||||
};
|
||||
|
||||
if (warnAboutOverwrite && getUnderstandsConfirmOverwrite())
|
||||
args.add ("--confirm-overwrite");
|
||||
|
||||
if (owner.title.isNotEmpty())
|
||||
args.add ("--title=" + owner.title);
|
||||
|
||||
if (selectMultipleFiles)
|
||||
{
|
||||
separator = ":";
|
||||
args.add ("--multiple");
|
||||
args.add ("--separator=" + separator);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isSave)
|
||||
args.add ("--save");
|
||||
}
|
||||
|
||||
if (isDirectory)
|
||||
args.add ("--directory");
|
||||
|
||||
if (owner.filters.isNotEmpty() && owner.filters != "*" && owner.filters != "*.*")
|
||||
{
|
||||
StringArray tokens;
|
||||
tokens.addTokens (owner.filters, ";,|", "\"");
|
||||
|
||||
args.add ("--file-filter=" + tokens.joinIntoString (" "));
|
||||
}
|
||||
|
||||
if (owner.startingFile.isDirectory())
|
||||
owner.startingFile.setAsCurrentWorkingDirectory();
|
||||
else if (owner.startingFile.getParentDirectory().exists())
|
||||
owner.startingFile.getParentDirectory().setAsCurrentWorkingDirectory();
|
||||
else
|
||||
File::getSpecialLocation (File::userHomeDirectory).setAsCurrentWorkingDirectory();
|
||||
|
||||
auto filename = owner.startingFile.getFileName();
|
||||
|
||||
if (! filename.isEmpty())
|
||||
args.add ("--filename=" + filename);
|
||||
|
||||
// supplying the window ID of the topmost window makes sure that Zenity pops up..
|
||||
if (uint64 topWindowID = getTopWindowID())
|
||||
setenv ("WINDOWID", String (topWindowID).toRawUTF8(), true);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
|
||||
};
|
||||
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
return false;
|
||||
#else
|
||||
static bool canUseNativeBox = exeIsAvailable ("zenity") || exeIsAvailable ("kdialog");
|
||||
return canUseNativeBox;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags, FilePreviewComponent*)
|
||||
{
|
||||
return std::make_shared<Native> (owner, flags);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,418 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
static NSMutableArray* createAllowedTypesArray (const StringArray& filters)
|
||||
{
|
||||
if (filters.size() == 0)
|
||||
return nil;
|
||||
|
||||
NSMutableArray* filterArray = [[[NSMutableArray alloc] init] autorelease];
|
||||
|
||||
for (int i = 0; i < filters.size(); ++i)
|
||||
{
|
||||
// From OS X 10.6 you can only specify allowed extensions, so any filters containing wildcards
|
||||
// must be of the form "*.extension"
|
||||
jassert (filters[i] == "*"
|
||||
|| (filters[i].startsWith ("*.") && filters[i].lastIndexOfChar ('*') == 0));
|
||||
const String f (filters[i].replace ("*.", ""));
|
||||
|
||||
if (f == "*")
|
||||
return nil;
|
||||
|
||||
[filterArray addObject: juceStringToNS (f)];
|
||||
}
|
||||
|
||||
return filterArray;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class FileChooser::Native final : public Component,
|
||||
public FileChooser::Pimpl
|
||||
{
|
||||
public:
|
||||
Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComponent)
|
||||
: owner (fileChooser), preview (previewComponent),
|
||||
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
|
||||
selectsFiles ((flags & FileBrowserComponent::canSelectFiles) != 0),
|
||||
isSave ((flags & FileBrowserComponent::saveMode) != 0),
|
||||
selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0)
|
||||
{
|
||||
setBounds (0, 0, 0, 0);
|
||||
setOpaque (true);
|
||||
|
||||
panel = [&]
|
||||
{
|
||||
if (SystemStats::isAppSandboxEnabled())
|
||||
return isSave ? [[NSSavePanel alloc] init]
|
||||
: [[NSOpenPanel alloc] init];
|
||||
|
||||
static SafeSavePanel safeSavePanel;
|
||||
static SafeOpenPanel safeOpenPanel;
|
||||
|
||||
return isSave ? [safeSavePanel.createInstance() init]
|
||||
: [safeOpenPanel.createInstance() init];
|
||||
}();
|
||||
|
||||
static DelegateClass delegateClass;
|
||||
|
||||
delegate = [delegateClass.createInstance() init];
|
||||
object_setInstanceVariable (delegate, "cppObject", this);
|
||||
|
||||
[panel setDelegate: delegate];
|
||||
|
||||
filters.addTokens (owner.filters.replaceCharacters (",:", ";;"), ";", String());
|
||||
filters.trim();
|
||||
filters.removeEmptyStrings();
|
||||
|
||||
auto* nsTitle = juceStringToNS (owner.title);
|
||||
[panel setTitle: nsTitle];
|
||||
[panel setReleasedWhenClosed: YES];
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
[panel setAllowedFileTypes: createAllowedTypesArray (filters)];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
if (! isSave)
|
||||
{
|
||||
auto* openPanel = static_cast<NSOpenPanel*> (panel);
|
||||
|
||||
[openPanel setCanChooseDirectories: selectsDirectories];
|
||||
[openPanel setCanChooseFiles: selectsFiles];
|
||||
[openPanel setAllowsMultipleSelection: selectMultiple];
|
||||
[openPanel setResolvesAliases: YES];
|
||||
[openPanel setMessage: nsTitle]; // equivalent to the title bar since 10.11
|
||||
|
||||
if (owner.treatFilePackagesAsDirs)
|
||||
[openPanel setTreatsFilePackagesAsDirectories: YES];
|
||||
}
|
||||
|
||||
if (preview != nullptr)
|
||||
{
|
||||
nsViewPreview = [[NSView alloc] initWithFrame: makeNSRect (preview->getLocalBounds())];
|
||||
[panel setAccessoryView: nsViewPreview];
|
||||
|
||||
preview->addToDesktop (0, (void*) nsViewPreview);
|
||||
preview->setVisible (true);
|
||||
|
||||
if (@available (macOS 10.11, *))
|
||||
{
|
||||
if (! isSave)
|
||||
{
|
||||
auto* openPanel = static_cast<NSOpenPanel*> (panel);
|
||||
[openPanel setAccessoryViewDisclosed: YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSave || selectsDirectories)
|
||||
[panel setCanCreateDirectories: YES];
|
||||
|
||||
[panel setLevel: NSModalPanelWindowLevel];
|
||||
|
||||
if (owner.startingFile.isDirectory())
|
||||
{
|
||||
startingDirectory = owner.startingFile.getFullPathName();
|
||||
}
|
||||
else
|
||||
{
|
||||
startingDirectory = owner.startingFile.getParentDirectory().getFullPathName();
|
||||
filename = owner.startingFile.getFileName();
|
||||
}
|
||||
|
||||
[panel setDirectoryURL: createNSURLFromFile (startingDirectory)];
|
||||
[panel setNameFieldStringValue: juceStringToNS (filename)];
|
||||
}
|
||||
|
||||
~Native() override
|
||||
{
|
||||
exitModalState (0);
|
||||
|
||||
if (preview != nullptr)
|
||||
preview->removeFromDesktop();
|
||||
|
||||
removeFromDesktop();
|
||||
|
||||
if (panel != nil)
|
||||
{
|
||||
[panel setDelegate: nil];
|
||||
|
||||
if (nsViewPreview != nil)
|
||||
{
|
||||
[panel setAccessoryView: nil];
|
||||
[nsViewPreview release];
|
||||
}
|
||||
|
||||
[panel close];
|
||||
}
|
||||
|
||||
if (delegate != nil)
|
||||
[delegate release];
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
if (panel != nil)
|
||||
{
|
||||
setAlwaysOnTop (WindowUtils::areThereAnyAlwaysOnTopWindows());
|
||||
addToDesktop (0);
|
||||
|
||||
enterModalState (true);
|
||||
|
||||
MessageManager::callAsync ([ref = SafePointer<Native> (this)]
|
||||
{
|
||||
if (ref == nullptr)
|
||||
return;
|
||||
|
||||
[ref->panel beginWithCompletionHandler: ^(NSInteger result)
|
||||
{
|
||||
if (auto* ptr = ref.getComponent())
|
||||
ptr->finished (result);
|
||||
}];
|
||||
|
||||
if (ref->preview != nullptr)
|
||||
ref->preview->toFront (true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
ensurePanelSafe();
|
||||
|
||||
std::unique_ptr<TemporaryMainMenuWithStandardCommands> tempMenu;
|
||||
|
||||
if (JUCEApplicationBase::isStandaloneApp())
|
||||
tempMenu = std::make_unique<TemporaryMainMenuWithStandardCommands> (preview);
|
||||
|
||||
jassert (panel != nil);
|
||||
auto result = [panel runModal];
|
||||
finished (result);
|
||||
#else
|
||||
jassertfalse;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool canModalEventBeSentToComponent (const Component* targetComponent) override
|
||||
{
|
||||
return TemporaryMainMenuWithStandardCommands::checkModalEvent (preview, targetComponent);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
typedef NSObject<NSOpenSavePanelDelegate> DelegateType;
|
||||
|
||||
static URL urlFromNSURL (NSURL* url)
|
||||
{
|
||||
const auto scheme = nsStringToJuce ([url scheme]);
|
||||
|
||||
auto pathComponents = StringArray::fromTokens (nsStringToJuce ([url path]), "/", {});
|
||||
|
||||
for (auto& component : pathComponents)
|
||||
component = URL::addEscapeChars (component, false);
|
||||
|
||||
return { scheme + "://" + pathComponents.joinIntoString ("/") };
|
||||
}
|
||||
|
||||
void finished (NSInteger result)
|
||||
{
|
||||
Array<URL> chooserResults;
|
||||
|
||||
exitModalState (0);
|
||||
|
||||
const auto okResult = []() -> NSInteger
|
||||
{
|
||||
if (@available (macOS 10.9, *))
|
||||
return NSModalResponseOK;
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
return NSFileHandlingPanelOKButton;
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}();
|
||||
|
||||
if (panel != nil && result == okResult)
|
||||
{
|
||||
auto addURLResult = [&chooserResults] (NSURL* urlToAdd)
|
||||
{
|
||||
chooserResults.add (urlFromNSURL (urlToAdd));
|
||||
};
|
||||
|
||||
if (isSave)
|
||||
{
|
||||
addURLResult ([panel URL]);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* openPanel = static_cast<NSOpenPanel*> (panel);
|
||||
auto urls = [openPanel URLs];
|
||||
|
||||
for (unsigned int i = 0; i < [urls count]; ++i)
|
||||
addURLResult ([urls objectAtIndex: i]);
|
||||
}
|
||||
}
|
||||
|
||||
owner.finished (chooserResults);
|
||||
}
|
||||
|
||||
BOOL shouldShowURL (const URL& urlToTest)
|
||||
{
|
||||
for (int i = filters.size(); --i >= 0;)
|
||||
if (urlToTest.getFileName().matchesWildcard (filters[i], true))
|
||||
return YES;
|
||||
|
||||
const auto f = urlToTest.getLocalFile();
|
||||
return f.isDirectory()
|
||||
&& ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: juceStringToNS (f.getFullPathName())];
|
||||
}
|
||||
|
||||
void panelSelectionDidChange ([[maybe_unused]] id sender)
|
||||
{
|
||||
jassert (sender == panel);
|
||||
|
||||
// NB: would need to extend FilePreviewComponent to handle the full list rather than just the first one
|
||||
if (preview != nullptr)
|
||||
preview->selectedFileChanged (File (getSelectedPaths()[0]));
|
||||
}
|
||||
|
||||
StringArray getSelectedPaths() const
|
||||
{
|
||||
if (panel == nullptr)
|
||||
return {};
|
||||
|
||||
StringArray paths;
|
||||
|
||||
if (isSave)
|
||||
{
|
||||
paths.add (nsStringToJuce ([[panel URL] path]));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* urls = [static_cast<NSOpenPanel*> (panel) URLs];
|
||||
|
||||
for (NSUInteger i = 0; i < [urls count]; ++i)
|
||||
paths.add (nsStringToJuce ([[urls objectAtIndex: i] path]));
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FileChooser& owner;
|
||||
FilePreviewComponent* preview;
|
||||
NSView* nsViewPreview = nullptr;
|
||||
bool selectsDirectories, selectsFiles, isSave, selectMultiple;
|
||||
|
||||
NSSavePanel* panel;
|
||||
DelegateType* delegate;
|
||||
|
||||
StringArray filters;
|
||||
String startingDirectory, filename;
|
||||
|
||||
void ensurePanelSafe()
|
||||
{
|
||||
// If you hit this, something (probably the plugin host) has modified the panel,
|
||||
// allowing the application to terminate while the panel's modal loop is running.
|
||||
// This is a very bad idea! Quitting from within the panel's modal loop may cause
|
||||
// your plugin/app destructor to run directly from within `runModally`, which will
|
||||
// dispose all app resources while they're still in use.
|
||||
// A safer alternative is to invoke the FileChooser with `launchAsync`, rather than
|
||||
// using the modal launchers.
|
||||
jassert ([panel preventsApplicationTerminationWhenModal]);
|
||||
}
|
||||
|
||||
static BOOL preventsApplicationTerminationWhenModal (id, SEL) { return YES; }
|
||||
|
||||
template <typename Base>
|
||||
struct SafeModalPanel : public ObjCClass<Base>
|
||||
{
|
||||
explicit SafeModalPanel (const char* name) : ObjCClass<Base> (name)
|
||||
{
|
||||
this->addMethod (@selector (preventsApplicationTerminationWhenModal),
|
||||
preventsApplicationTerminationWhenModal);
|
||||
|
||||
this->registerClass();
|
||||
}
|
||||
};
|
||||
|
||||
struct SafeSavePanel : SafeModalPanel<NSSavePanel>
|
||||
{
|
||||
SafeSavePanel() : SafeModalPanel ("SafeSavePanel_") {}
|
||||
};
|
||||
|
||||
struct SafeOpenPanel : SafeModalPanel<NSOpenPanel>
|
||||
{
|
||||
SafeOpenPanel() : SafeModalPanel ("SafeOpenPanel_") {}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct DelegateClass final : public ObjCClass<DelegateType>
|
||||
{
|
||||
DelegateClass() : ObjCClass<DelegateType> ("JUCEFileChooser_")
|
||||
{
|
||||
addIvar<Native*> ("cppObject");
|
||||
|
||||
addMethod (@selector (panel:shouldEnableURL:), shouldEnableURL);
|
||||
addMethod (@selector (panelSelectionDidChange:), panelSelectionDidChange);
|
||||
|
||||
addProtocol (@protocol (NSOpenSavePanelDelegate));
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static BOOL shouldEnableURL (id self, SEL, id /*sender*/, NSURL* url)
|
||||
{
|
||||
return getIvar<Native*> (self, "cppObject")->shouldShowURL (urlFromNSURL (url));
|
||||
}
|
||||
|
||||
static void panelSelectionDidChange (id self, SEL, id sender)
|
||||
{
|
||||
getIvar<Native*> (self, "cppObject")->panelSelectionDidChange (sender);
|
||||
}
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native)
|
||||
};
|
||||
|
||||
std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
||||
FilePreviewComponent* preview)
|
||||
{
|
||||
return std::make_shared<FileChooser::Native> (owner, flags, preview);
|
||||
}
|
||||
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+902
@@ -0,0 +1,902 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
// Implemented in juce_Messaging_windows.cpp
|
||||
namespace detail
|
||||
{
|
||||
bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages);
|
||||
} // namespace detail
|
||||
|
||||
class Win32NativeFileChooser final : private Thread
|
||||
{
|
||||
public:
|
||||
enum { charsAvailableForResult = 32768 };
|
||||
|
||||
Win32NativeFileChooser (Component* parent, int flags, FilePreviewComponent* previewComp,
|
||||
const File& startingFile, const String& titleToUse,
|
||||
const String& filtersToUse)
|
||||
: Thread ("Native Win32 FileChooser"),
|
||||
owner (parent),
|
||||
title (titleToUse),
|
||||
filtersString (filtersToUse.replaceCharacter (',', ';')),
|
||||
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
|
||||
// When dealing with directories, it is not possible to 'Save' them. However, one can 'Open' a directory in order to save into it.
|
||||
// If the 'saveMode' and 'canSelectDirectories' flags are both present, create an FileOpenDialog instead of an FileSaveDialog.
|
||||
isSave ((flags & FileBrowserComponent::saveMode) != 0 && ! selectsDirectories),
|
||||
warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0),
|
||||
selectMultiple ((flags & FileBrowserComponent::canSelectMultipleItems) != 0)
|
||||
{
|
||||
auto parentDirectory = startingFile.getParentDirectory();
|
||||
|
||||
// Handle nonexistent root directories in the same way as existing ones
|
||||
files.calloc (static_cast<size_t> (charsAvailableForResult) + 1);
|
||||
|
||||
if (startingFile.isDirectory() || startingFile.isRoot())
|
||||
{
|
||||
initialPath = startingFile.getFullPathName();
|
||||
}
|
||||
else
|
||||
{
|
||||
startingFile.getFileName().copyToUTF16 (files,
|
||||
static_cast<size_t> (charsAvailableForResult) * sizeof (WCHAR));
|
||||
initialPath = parentDirectory.getFullPathName();
|
||||
}
|
||||
|
||||
if (! selectsDirectories)
|
||||
{
|
||||
if (previewComp != nullptr)
|
||||
customComponent.reset (new CustomComponentHolder (previewComp));
|
||||
|
||||
setupFilters();
|
||||
}
|
||||
}
|
||||
|
||||
~Win32NativeFileChooser() override
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
|
||||
while (isThreadRunning())
|
||||
{
|
||||
if (! detail::dispatchNextMessageOnSystemQueue (true))
|
||||
Thread::sleep (1);
|
||||
}
|
||||
}
|
||||
|
||||
void open (bool async)
|
||||
{
|
||||
results.clear();
|
||||
|
||||
// the thread should not be running
|
||||
nativeDialogRef.set (nullptr);
|
||||
|
||||
if (async)
|
||||
{
|
||||
jassert (! isThreadRunning());
|
||||
|
||||
startThread();
|
||||
}
|
||||
else
|
||||
{
|
||||
results = openDialog (false);
|
||||
owner->exitModalState (results.size() > 0 ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
ScopedLock lock (deletingDialog);
|
||||
|
||||
customComponent = nullptr;
|
||||
shouldCancel = true;
|
||||
|
||||
if (auto hwnd = nativeDialogRef.get())
|
||||
PostMessage (hwnd, WM_CLOSE, 0, 0);
|
||||
}
|
||||
|
||||
Component* getCustomComponent() { return customComponent.get(); }
|
||||
|
||||
Array<URL> results;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class CustomComponentHolder final : public Component
|
||||
{
|
||||
public:
|
||||
CustomComponentHolder (Component* const customComp)
|
||||
{
|
||||
setVisible (true);
|
||||
setOpaque (true);
|
||||
addAndMakeVisible (customComp);
|
||||
setSize (jlimit (20, 800, customComp->getWidth()), customComp->getHeight());
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (Colours::lightgrey);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
if (Component* const c = getChildComponent (0))
|
||||
c->setBounds (getLocalBounds());
|
||||
}
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomComponentHolder)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
const Component::SafePointer<Component> owner;
|
||||
String title, filtersString;
|
||||
std::unique_ptr<CustomComponentHolder> customComponent;
|
||||
String initialPath, returnedString;
|
||||
|
||||
CriticalSection deletingDialog;
|
||||
|
||||
bool selectsDirectories, isSave, warnAboutOverwrite, selectMultiple;
|
||||
|
||||
HeapBlock<WCHAR> files;
|
||||
HeapBlock<WCHAR> filters;
|
||||
|
||||
Atomic<HWND> nativeDialogRef { nullptr };
|
||||
bool shouldCancel = false;
|
||||
|
||||
struct FreeLPWSTR
|
||||
{
|
||||
void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); }
|
||||
};
|
||||
|
||||
bool showDialog (IFileDialog& dialog)
|
||||
{
|
||||
FILEOPENDIALOGOPTIONS flags = {};
|
||||
|
||||
if (FAILED (dialog.GetOptions (&flags)))
|
||||
return false;
|
||||
|
||||
const auto setBit = [] (FILEOPENDIALOGOPTIONS& field, bool value, FILEOPENDIALOGOPTIONS option)
|
||||
{
|
||||
if (value)
|
||||
field |= option;
|
||||
else
|
||||
field &= ~option;
|
||||
};
|
||||
|
||||
setBit (flags, selectsDirectories, FOS_PICKFOLDERS);
|
||||
setBit (flags, warnAboutOverwrite, FOS_OVERWRITEPROMPT);
|
||||
setBit (flags, selectMultiple, FOS_ALLOWMULTISELECT);
|
||||
setBit (flags, customComponent != nullptr, FOS_FORCEPREVIEWPANEON);
|
||||
|
||||
if (FAILED (dialog.SetOptions (flags)) || FAILED (dialog.SetTitle (title.toUTF16())))
|
||||
return false;
|
||||
|
||||
PIDLIST_ABSOLUTE pidl = {};
|
||||
|
||||
if (FAILED (SHParseDisplayName (initialPath.toWideCharPointer(), nullptr, &pidl, SFGAO_FOLDER, nullptr)))
|
||||
{
|
||||
LPWSTR ptr = nullptr;
|
||||
auto result = SHGetKnownFolderPath (FOLDERID_Desktop, 0, nullptr, &ptr);
|
||||
std::unique_ptr<WCHAR, FreeLPWSTR> desktopPath (ptr);
|
||||
|
||||
if (FAILED (result))
|
||||
return false;
|
||||
|
||||
if (FAILED (SHParseDisplayName (desktopPath.get(), nullptr, &pidl, SFGAO_FOLDER, nullptr)))
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto item = [&]
|
||||
{
|
||||
ComSmartPtr<IShellItem> ptr;
|
||||
SHCreateShellItem (nullptr, nullptr, pidl, ptr.resetAndGetPointerAddress());
|
||||
return ptr;
|
||||
}();
|
||||
|
||||
if (item != nullptr)
|
||||
{
|
||||
dialog.SetDefaultFolder (item);
|
||||
|
||||
if (! initialPath.isEmpty())
|
||||
dialog.SetFolder (item);
|
||||
}
|
||||
|
||||
String filename (files.getData());
|
||||
|
||||
if (FAILED (dialog.SetFileName (filename.toWideCharPointer())))
|
||||
return false;
|
||||
|
||||
auto extension = getDefaultFileExtension (filename);
|
||||
|
||||
if (extension.isNotEmpty() && FAILED (dialog.SetDefaultExtension (extension.toWideCharPointer())))
|
||||
return false;
|
||||
|
||||
const COMDLG_FILTERSPEC spec[] { { filtersString.toWideCharPointer(), filtersString.toWideCharPointer() } };
|
||||
|
||||
if (! selectsDirectories && FAILED (dialog.SetFileTypes (numElementsInArray (spec), spec)))
|
||||
return false;
|
||||
|
||||
struct Events final : public ComBaseClassHelper<IFileDialogEvents>
|
||||
{
|
||||
explicit Events (Win32NativeFileChooser& o) : owner (o) {}
|
||||
|
||||
JUCE_COMRESULT OnTypeChange (IFileDialog* d) override { return updateHwnd (d); }
|
||||
JUCE_COMRESULT OnFolderChanging (IFileDialog* d, IShellItem*) override { return updateHwnd (d); }
|
||||
JUCE_COMRESULT OnFileOk (IFileDialog* d) override { return updateHwnd (d); }
|
||||
JUCE_COMRESULT OnFolderChange (IFileDialog* d) override { return updateHwnd (d); }
|
||||
JUCE_COMRESULT OnSelectionChange (IFileDialog* d) override { focusWorkaround(); return updateHwnd (d); }
|
||||
JUCE_COMRESULT OnShareViolation (IFileDialog* d, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) override { return updateHwnd (d); }
|
||||
JUCE_COMRESULT OnOverwrite (IFileDialog* d, IShellItem*, FDE_OVERWRITE_RESPONSE*) override { return updateHwnd (d); }
|
||||
|
||||
/* Workaround for a bug in Vista+, OpenFileDialog will truncate the initialFile text.
|
||||
Moving the caret along the full length of the text and back will reveal the full string.
|
||||
*/
|
||||
void focusWorkaround()
|
||||
{
|
||||
if (! defaultFileNameTextCaretMoved)
|
||||
{
|
||||
auto makeKeyInput = [] (WORD vk, bool pressed)
|
||||
{
|
||||
INPUT i;
|
||||
|
||||
ZeroMemory (&i, sizeof (INPUT));
|
||||
|
||||
i.type = INPUT_KEYBOARD;
|
||||
i.ki.wVk = vk;
|
||||
i.ki.dwFlags = pressed ? 0 : KEYEVENTF_KEYUP;
|
||||
|
||||
return i;
|
||||
};
|
||||
|
||||
INPUT inputs[] = {
|
||||
makeKeyInput (VK_HOME, true),
|
||||
makeKeyInput (VK_HOME, false),
|
||||
makeKeyInput (VK_END, true),
|
||||
makeKeyInput (VK_END, false),
|
||||
};
|
||||
|
||||
SendInput ((UINT) std::size (inputs), inputs, sizeof (INPUT));
|
||||
defaultFileNameTextCaretMoved = true;
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_COMRESULT updateHwnd (IFileDialog* d)
|
||||
{
|
||||
HWND hwnd = nullptr;
|
||||
|
||||
if (auto window = ComSmartPtr<IFileDialog> { d }.getInterface<IOleWindow>())
|
||||
window->GetWindow (&hwnd);
|
||||
|
||||
ScopedLock lock (owner.deletingDialog);
|
||||
|
||||
if (owner.shouldCancel)
|
||||
d->Close (S_FALSE);
|
||||
else if (hwnd != nullptr)
|
||||
owner.nativeDialogRef = hwnd;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
bool defaultFileNameTextCaretMoved = false;
|
||||
Win32NativeFileChooser& owner;
|
||||
};
|
||||
|
||||
{
|
||||
ScopedLock lock (deletingDialog);
|
||||
|
||||
if (shouldCancel)
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto result = [&]
|
||||
{
|
||||
struct ScopedAdvise
|
||||
{
|
||||
ScopedAdvise (IFileDialog& d, Events& events) : dialog (d) { dialog.Advise (&events, &cookie); }
|
||||
~ScopedAdvise() { dialog.Unadvise (cookie); }
|
||||
IFileDialog& dialog;
|
||||
DWORD cookie = 0;
|
||||
};
|
||||
|
||||
Events events { *this };
|
||||
ScopedAdvise scope { dialog, events };
|
||||
return dialog.Show (GetActiveWindow()) == S_OK;
|
||||
}();
|
||||
|
||||
ScopedLock lock (deletingDialog);
|
||||
nativeDialogRef = nullptr;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Array<URL> openDialogVistaAndUp()
|
||||
{
|
||||
const auto getUrl = [] (IShellItem& item)
|
||||
{
|
||||
LPWSTR ptr = nullptr;
|
||||
|
||||
if (item.GetDisplayName (SIGDN_FILESYSPATH, &ptr) != S_OK)
|
||||
return URL();
|
||||
|
||||
const auto path = std::unique_ptr<WCHAR, FreeLPWSTR> { ptr };
|
||||
return URL (File (String (path.get())));
|
||||
};
|
||||
|
||||
if (isSave)
|
||||
{
|
||||
const auto dialog = [&]
|
||||
{
|
||||
ComSmartPtr<IFileDialog> ptr;
|
||||
ptr.CoCreateInstance (CLSID_FileSaveDialog, CLSCTX_INPROC_SERVER);
|
||||
return ptr;
|
||||
}();
|
||||
|
||||
if (dialog == nullptr)
|
||||
return {};
|
||||
|
||||
showDialog (*dialog);
|
||||
|
||||
const auto item = [&]
|
||||
{
|
||||
ComSmartPtr<IShellItem> ptr;
|
||||
dialog->GetResult (ptr.resetAndGetPointerAddress());
|
||||
return ptr;
|
||||
}();
|
||||
|
||||
if (item == nullptr)
|
||||
return {};
|
||||
|
||||
const auto url = getUrl (*item);
|
||||
|
||||
if (url.isEmpty())
|
||||
return {};
|
||||
|
||||
return { url };
|
||||
}
|
||||
|
||||
const auto dialog = [&]
|
||||
{
|
||||
ComSmartPtr<IFileOpenDialog> ptr;
|
||||
ptr.CoCreateInstance (CLSID_FileOpenDialog, CLSCTX_INPROC_SERVER);
|
||||
return ptr;
|
||||
}();
|
||||
|
||||
if (dialog == nullptr)
|
||||
return {};
|
||||
|
||||
showDialog (*dialog);
|
||||
|
||||
const auto items = [&]
|
||||
{
|
||||
ComSmartPtr<IShellItemArray> ptr;
|
||||
dialog->GetResults (ptr.resetAndGetPointerAddress());
|
||||
return ptr;
|
||||
}();
|
||||
|
||||
if (items == nullptr)
|
||||
return {};
|
||||
|
||||
Array<URL> result;
|
||||
|
||||
DWORD numItems = 0;
|
||||
items->GetCount (&numItems);
|
||||
|
||||
for (DWORD i = 0; i < numItems; ++i)
|
||||
{
|
||||
ComSmartPtr<IShellItem> scope;
|
||||
items->GetItemAt (i, scope.resetAndGetPointerAddress());
|
||||
|
||||
if (scope != nullptr)
|
||||
{
|
||||
const auto url = getUrl (*scope);
|
||||
|
||||
if (! url.isEmpty())
|
||||
result.add (url);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Array<URL> openDialogPreVista (bool async)
|
||||
{
|
||||
Array<URL> selections;
|
||||
|
||||
if (selectsDirectories)
|
||||
{
|
||||
BROWSEINFO bi = {};
|
||||
bi.hwndOwner = GetActiveWindow();
|
||||
bi.pszDisplayName = files;
|
||||
bi.lpszTitle = title.toWideCharPointer();
|
||||
bi.lParam = (LPARAM) this;
|
||||
bi.lpfn = browseCallbackProc;
|
||||
#ifdef BIF_USENEWUI
|
||||
bi.ulFlags = BIF_USENEWUI | BIF_VALIDATE;
|
||||
#else
|
||||
bi.ulFlags = 0x50;
|
||||
#endif
|
||||
|
||||
LPITEMIDLIST list = SHBrowseForFolder (&bi);
|
||||
|
||||
if (! SHGetPathFromIDListW (list, files))
|
||||
{
|
||||
files[0] = 0;
|
||||
returnedString.clear();
|
||||
}
|
||||
|
||||
LPMALLOC al;
|
||||
|
||||
if (list != nullptr && SUCCEEDED (SHGetMalloc (&al)))
|
||||
al->Free (list);
|
||||
|
||||
if (files[0] != 0)
|
||||
{
|
||||
File result (String (files.get()));
|
||||
|
||||
if (returnedString.isNotEmpty())
|
||||
result = result.getSiblingFile (returnedString);
|
||||
|
||||
selections.add (URL (result));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OPENFILENAMEW of = {};
|
||||
|
||||
#ifdef OPENFILENAME_SIZE_VERSION_400W
|
||||
of.lStructSize = OPENFILENAME_SIZE_VERSION_400W;
|
||||
#else
|
||||
of.lStructSize = sizeof (of);
|
||||
#endif
|
||||
|
||||
if (files[0] != 0)
|
||||
{
|
||||
auto startingFile = File (initialPath).getChildFile (String (files.get()));
|
||||
startingFile.getFullPathName().copyToUTF16 (files, charsAvailableForResult * sizeof (WCHAR));
|
||||
}
|
||||
|
||||
of.hwndOwner = GetActiveWindow();
|
||||
of.lpstrFilter = filters.getData();
|
||||
of.nFilterIndex = 1;
|
||||
of.lpstrFile = files;
|
||||
of.nMaxFile = (DWORD) charsAvailableForResult;
|
||||
of.lpstrInitialDir = initialPath.toWideCharPointer();
|
||||
of.lpstrTitle = title.toWideCharPointer();
|
||||
of.Flags = getOpenFilenameFlags (async);
|
||||
of.lCustData = (LPARAM) this;
|
||||
of.lpfnHook = &openCallback;
|
||||
|
||||
if (isSave)
|
||||
{
|
||||
auto extension = getDefaultFileExtension (files.getData());
|
||||
|
||||
if (extension.isNotEmpty())
|
||||
of.lpstrDefExt = extension.toWideCharPointer();
|
||||
|
||||
if (! GetSaveFileName (&of))
|
||||
return {};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! GetOpenFileName (&of))
|
||||
return {};
|
||||
}
|
||||
|
||||
if (selectMultiple && of.nFileOffset > 0 && files[of.nFileOffset - 1] == 0)
|
||||
{
|
||||
const WCHAR* filename = files + of.nFileOffset;
|
||||
|
||||
while (*filename != 0)
|
||||
{
|
||||
selections.add (URL (File (String (files.get())).getChildFile (String (filename))));
|
||||
filename += wcslen (filename) + 1;
|
||||
}
|
||||
}
|
||||
else if (files[0] != 0)
|
||||
{
|
||||
selections.add (URL (File (String (files.get()))));
|
||||
}
|
||||
}
|
||||
|
||||
return selections;
|
||||
}
|
||||
|
||||
Array<URL> openDialog (bool async)
|
||||
{
|
||||
struct Remover
|
||||
{
|
||||
explicit Remover (Win32NativeFileChooser& chooser) : item (chooser) {}
|
||||
~Remover() { getNativeDialogList().removeValue (&item); }
|
||||
|
||||
Win32NativeFileChooser& item;
|
||||
};
|
||||
|
||||
const Remover remover (*this);
|
||||
|
||||
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista
|
||||
&& customComponent == nullptr)
|
||||
{
|
||||
return openDialogVistaAndUp();
|
||||
}
|
||||
|
||||
return openDialogPreVista (async);
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
results = [&]
|
||||
{
|
||||
struct ScopedCoInitialize
|
||||
{
|
||||
// IUnknown_GetWindow will only succeed when instantiated in a single-thread apartment
|
||||
ScopedCoInitialize() { [[maybe_unused]] const auto result = CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); }
|
||||
~ScopedCoInitialize() { CoUninitialize(); }
|
||||
};
|
||||
|
||||
ScopedCoInitialize scope;
|
||||
|
||||
return openDialog (true);
|
||||
}();
|
||||
|
||||
auto safeOwner = owner;
|
||||
auto resultCode = results.size() > 0 ? 1 : 0;
|
||||
|
||||
MessageManager::callAsync ([resultCode, safeOwner]
|
||||
{
|
||||
if (safeOwner != nullptr)
|
||||
safeOwner->exitModalState (resultCode);
|
||||
});
|
||||
}
|
||||
|
||||
static HashMap<HWND, Win32NativeFileChooser*>& getNativeDialogList()
|
||||
{
|
||||
static HashMap<HWND, Win32NativeFileChooser*> dialogs;
|
||||
return dialogs;
|
||||
}
|
||||
|
||||
static Win32NativeFileChooser* getNativePointerForDialog (HWND hwnd)
|
||||
{
|
||||
return getNativeDialogList()[hwnd];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void setupFilters()
|
||||
{
|
||||
const size_t filterSpaceNumChars = 2048;
|
||||
filters.calloc (filterSpaceNumChars);
|
||||
|
||||
const size_t bytesWritten = filtersString.copyToUTF16 (filters.getData(), filterSpaceNumChars * sizeof (WCHAR));
|
||||
filtersString.copyToUTF16 (filters + (bytesWritten / sizeof (WCHAR)),
|
||||
((filterSpaceNumChars - 1) * sizeof (WCHAR) - bytesWritten));
|
||||
|
||||
for (size_t i = 0; i < filterSpaceNumChars; ++i)
|
||||
if (filters[i] == '|')
|
||||
filters[i] = 0;
|
||||
}
|
||||
|
||||
DWORD getOpenFilenameFlags (bool async)
|
||||
{
|
||||
DWORD ofFlags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY | OFN_ENABLESIZING;
|
||||
|
||||
if (warnAboutOverwrite)
|
||||
ofFlags |= OFN_OVERWRITEPROMPT;
|
||||
|
||||
if (selectMultiple)
|
||||
ofFlags |= OFN_ALLOWMULTISELECT;
|
||||
|
||||
if (async || customComponent != nullptr)
|
||||
ofFlags |= OFN_ENABLEHOOK;
|
||||
|
||||
return ofFlags;
|
||||
}
|
||||
|
||||
String getDefaultFileExtension (const String& filename) const
|
||||
{
|
||||
const auto extension = filename.contains (".") ? filename.fromLastOccurrenceOf (".", false, false)
|
||||
: String();
|
||||
|
||||
if (! extension.isEmpty())
|
||||
return extension;
|
||||
|
||||
auto tokens = StringArray::fromTokens (filtersString, ";,", "\"'");
|
||||
tokens.trim();
|
||||
tokens.removeEmptyStrings();
|
||||
|
||||
if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty())
|
||||
return tokens[0].fromFirstOccurrenceOf (".", false, false);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void initialised (HWND hWnd)
|
||||
{
|
||||
SendMessage (hWnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) initialPath.toWideCharPointer());
|
||||
initDialog (hWnd);
|
||||
}
|
||||
|
||||
void validateFailed (const String& path)
|
||||
{
|
||||
returnedString = path;
|
||||
}
|
||||
|
||||
void initDialog (HWND hdlg)
|
||||
{
|
||||
ScopedLock lock (deletingDialog);
|
||||
getNativeDialogList().set (hdlg, this);
|
||||
|
||||
if (shouldCancel)
|
||||
{
|
||||
EndDialog (hdlg, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
nativeDialogRef.set (hdlg);
|
||||
|
||||
if (customComponent != nullptr)
|
||||
{
|
||||
Component::SafePointer<Component> safeCustomComponent (customComponent.get());
|
||||
|
||||
RECT dialogScreenRect, dialogClientRect;
|
||||
GetWindowRect (hdlg, &dialogScreenRect);
|
||||
GetClientRect (hdlg, &dialogClientRect);
|
||||
|
||||
auto screenRectangle = Rectangle<int>::leftTopRightBottom (dialogScreenRect.left, dialogScreenRect.top,
|
||||
dialogScreenRect.right, dialogScreenRect.bottom);
|
||||
|
||||
auto scale = Desktop::getInstance().getDisplays().getDisplayForRect (screenRectangle, true)->scale;
|
||||
auto physicalComponentWidth = roundToInt (safeCustomComponent->getWidth() * scale);
|
||||
|
||||
SetWindowPos (hdlg, nullptr, screenRectangle.getX(), screenRectangle.getY(),
|
||||
physicalComponentWidth + jmax (150, screenRectangle.getWidth()),
|
||||
jmax (150, screenRectangle.getHeight()),
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);
|
||||
|
||||
auto appendCustomComponent = [safeCustomComponent, dialogClientRect, scale, hdlg]() mutable
|
||||
{
|
||||
if (safeCustomComponent != nullptr)
|
||||
{
|
||||
auto scaledClientRectangle = Rectangle<int>::leftTopRightBottom (dialogClientRect.left, dialogClientRect.top,
|
||||
dialogClientRect.right, dialogClientRect.bottom) / scale;
|
||||
|
||||
safeCustomComponent->setBounds (scaledClientRectangle.getRight(), scaledClientRectangle.getY(),
|
||||
safeCustomComponent->getWidth(), scaledClientRectangle.getHeight());
|
||||
safeCustomComponent->addToDesktop (0, hdlg);
|
||||
}
|
||||
};
|
||||
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
appendCustomComponent();
|
||||
else
|
||||
MessageManager::callAsync (appendCustomComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void destroyDialog (HWND hdlg)
|
||||
{
|
||||
ScopedLock exiting (deletingDialog);
|
||||
|
||||
getNativeDialogList().remove (hdlg);
|
||||
nativeDialogRef.set (nullptr);
|
||||
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
customComponent = nullptr;
|
||||
else
|
||||
MessageManager::callAsync ([this] { customComponent = nullptr; });
|
||||
}
|
||||
|
||||
void selectionChanged (HWND hdlg)
|
||||
{
|
||||
ScopedLock lock (deletingDialog);
|
||||
|
||||
if (customComponent != nullptr && ! shouldCancel)
|
||||
{
|
||||
if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (customComponent->getChildComponent (0)))
|
||||
{
|
||||
WCHAR path [MAX_PATH * 2] = { 0 };
|
||||
CommDlg_OpenSave_GetFilePath (hdlg, (LPARAM) &path, MAX_PATH);
|
||||
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
{
|
||||
comp->selectedFileChanged (File (path));
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageManager::callAsync ([safeComp = Component::SafePointer<FilePreviewComponent> { comp },
|
||||
selectedFile = File { path }]() mutable
|
||||
{
|
||||
if (safeComp != nullptr)
|
||||
safeComp->selectedFileChanged (selectedFile);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static int CALLBACK browseCallbackProc (HWND hWnd, UINT msg, LPARAM lParam, LPARAM lpData)
|
||||
{
|
||||
auto* self = reinterpret_cast<Win32NativeFileChooser*> (lpData);
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case BFFM_INITIALIZED: self->initialised (hWnd); break;
|
||||
case BFFM_VALIDATEFAILEDW: self->validateFailed (String ((LPCWSTR) lParam)); break;
|
||||
case BFFM_VALIDATEFAILEDA: self->validateFailed (String ((const char*) lParam)); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static UINT_PTR CALLBACK openCallback (HWND hwnd, UINT uiMsg, WPARAM /*wParam*/, LPARAM lParam)
|
||||
{
|
||||
auto hdlg = getDialogFromHWND (hwnd);
|
||||
|
||||
switch (uiMsg)
|
||||
{
|
||||
case WM_INITDIALOG:
|
||||
{
|
||||
if (auto* self = reinterpret_cast<Win32NativeFileChooser*> (((OPENFILENAMEW*) lParam)->lCustData))
|
||||
self->initDialog (hdlg);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_DESTROY:
|
||||
{
|
||||
if (auto* self = getNativeDialogList()[hdlg])
|
||||
self->destroyDialog (hdlg);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_NOTIFY:
|
||||
{
|
||||
auto ofn = reinterpret_cast<LPOFNOTIFY> (lParam);
|
||||
|
||||
if (ofn->hdr.code == CDN_SELCHANGE)
|
||||
if (auto* self = reinterpret_cast<Win32NativeFileChooser*> (ofn->lpOFN->lCustData))
|
||||
self->selectionChanged (hdlg);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static HWND getDialogFromHWND (HWND hwnd)
|
||||
{
|
||||
if (hwnd == nullptr)
|
||||
return nullptr;
|
||||
|
||||
HWND dialogH = GetParent (hwnd);
|
||||
|
||||
if (dialogH == nullptr)
|
||||
dialogH = hwnd;
|
||||
|
||||
return dialogH;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32NativeFileChooser)
|
||||
};
|
||||
|
||||
class FileChooser::Native final : public std::enable_shared_from_this<Native>,
|
||||
public Component,
|
||||
public FileChooser::Pimpl
|
||||
{
|
||||
public:
|
||||
Native (FileChooser& fileChooser, int flagsIn, FilePreviewComponent* previewComp)
|
||||
: owner (fileChooser),
|
||||
nativeFileChooser (std::make_unique<Win32NativeFileChooser> (this, flagsIn, previewComp, fileChooser.startingFile,
|
||||
fileChooser.title, fileChooser.filters))
|
||||
{
|
||||
auto mainMon = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
|
||||
|
||||
setBounds (mainMon.getX() + mainMon.getWidth() / 4,
|
||||
mainMon.getY() + mainMon.getHeight() / 4,
|
||||
0, 0);
|
||||
|
||||
setOpaque (true);
|
||||
setAlwaysOnTop (WindowUtils::areThereAnyAlwaysOnTopWindows());
|
||||
addToDesktop (0);
|
||||
}
|
||||
|
||||
~Native() override
|
||||
{
|
||||
exitModalState (0);
|
||||
nativeFileChooser->cancel();
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
std::weak_ptr<Native> safeThis = shared_from_this();
|
||||
|
||||
enterModalState (true, ModalCallbackFunction::create ([safeThis] (int)
|
||||
{
|
||||
if (auto locked = safeThis.lock())
|
||||
locked->owner.finished (locked->nativeFileChooser->results);
|
||||
}));
|
||||
|
||||
nativeFileChooser->open (true);
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
enterModalState (true);
|
||||
nativeFileChooser->open (false);
|
||||
exitModalState (nativeFileChooser->results.size() > 0 ? 1 : 0);
|
||||
nativeFileChooser->cancel();
|
||||
|
||||
owner.finished (nativeFileChooser->results);
|
||||
#else
|
||||
jassertfalse;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool canModalEventBeSentToComponent (const Component* targetComponent) override
|
||||
{
|
||||
if (targetComponent == nullptr)
|
||||
return false;
|
||||
|
||||
if (targetComponent == nativeFileChooser->getCustomComponent())
|
||||
return true;
|
||||
|
||||
return targetComponent->findParentComponentOfClass<FilePreviewComponent>() != nullptr;
|
||||
}
|
||||
|
||||
void inputAttemptWhenModal() override {}
|
||||
|
||||
private:
|
||||
FileChooser& owner;
|
||||
std::shared_ptr<Win32NativeFileChooser> nativeFileChooser;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool FileChooser::isPlatformDialogAvailable()
|
||||
{
|
||||
#if JUCE_DISABLE_NATIVE_FILECHOOSERS
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::shared_ptr<FileChooser::Pimpl> FileChooser::showPlatformDialog (FileChooser& owner, int flags,
|
||||
FilePreviewComponent* preview)
|
||||
{
|
||||
return std::make_shared<FileChooser::Native> (owner, flags, preview);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,849 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
const auto menuItemInvokedSelector = @selector (menuItemInvoked:);
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
//==============================================================================
|
||||
struct JuceMainMenuBarHolder final : private DeletedAtShutdown
|
||||
{
|
||||
JuceMainMenuBarHolder()
|
||||
: mainMenuBar ([[NSMenu alloc] initWithTitle: nsStringLiteral ("MainMenu")])
|
||||
{
|
||||
auto item = [mainMenuBar addItemWithTitle: nsStringLiteral ("Apple")
|
||||
action: nil
|
||||
keyEquivalent: nsEmptyString()];
|
||||
|
||||
auto appMenu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Apple")];
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
[mainMenuBar setSubmenu: appMenu forItem: item];
|
||||
[appMenu release];
|
||||
|
||||
[NSApp setMainMenu: mainMenuBar];
|
||||
}
|
||||
|
||||
~JuceMainMenuBarHolder()
|
||||
{
|
||||
clearSingletonInstance();
|
||||
|
||||
[NSApp setMainMenu: nil];
|
||||
[mainMenuBar release];
|
||||
}
|
||||
|
||||
NSMenu* mainMenuBar = nil;
|
||||
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED (JuceMainMenuBarHolder, true)
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (JuceMainMenuBarHolder)
|
||||
|
||||
//==============================================================================
|
||||
class JuceMainMenuHandler final : private MenuBarModel::Listener,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
JuceMainMenuHandler()
|
||||
{
|
||||
static JuceMenuCallbackClass cls;
|
||||
callback = [cls.createInstance() init];
|
||||
JuceMenuCallbackClass::setOwner (callback, this);
|
||||
}
|
||||
|
||||
~JuceMainMenuHandler() override
|
||||
{
|
||||
setMenu (nullptr, nullptr, String());
|
||||
|
||||
jassert (instance == this);
|
||||
instance = nullptr;
|
||||
|
||||
[callback release];
|
||||
}
|
||||
|
||||
void setMenu (MenuBarModel* const newMenuBarModel,
|
||||
const PopupMenu* newExtraAppleMenuItems,
|
||||
const String& recentItemsName)
|
||||
{
|
||||
recentItemsMenuName = recentItemsName;
|
||||
|
||||
if (currentModel != newMenuBarModel)
|
||||
{
|
||||
if (currentModel != nullptr)
|
||||
currentModel->removeListener (this);
|
||||
|
||||
currentModel = newMenuBarModel;
|
||||
|
||||
if (currentModel != nullptr)
|
||||
currentModel->addListener (this);
|
||||
|
||||
menuBarItemsChanged (nullptr);
|
||||
}
|
||||
|
||||
extraAppleMenuItems.reset (createCopyIfNotNull (newExtraAppleMenuItems));
|
||||
}
|
||||
|
||||
void addTopLevelMenu (NSMenu* parent, const PopupMenu& child, const String& name, int menuId, int topLevelIndex)
|
||||
{
|
||||
NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name)
|
||||
action: nil
|
||||
keyEquivalent: nsEmptyString()];
|
||||
|
||||
NSMenu* sub = createMenu (child, name, menuId, topLevelIndex, true);
|
||||
|
||||
[parent setSubmenu: sub forItem: item];
|
||||
[sub release];
|
||||
}
|
||||
|
||||
void updateTopLevelMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy, const String& name, int menuId, int topLevelIndex)
|
||||
{
|
||||
// Note: This method used to update the contents of the existing menu in-place, but that caused
|
||||
// weird side-effects which messed-up keyboard focus when switching between windows. By creating
|
||||
// a new menu and replacing the old one with it, that problem seems to be avoided..
|
||||
NSMenu* menu = [[NSMenu alloc] initWithTitle: juceStringToNS (name)];
|
||||
|
||||
for (PopupMenu::MenuItemIterator iter (menuToCopy); iter.next();)
|
||||
addMenuItem (iter, menu, menuId, topLevelIndex);
|
||||
|
||||
[menu update];
|
||||
|
||||
removeItemRecursive ([parentItem submenu]);
|
||||
[parentItem setSubmenu: menu];
|
||||
|
||||
[menu release];
|
||||
}
|
||||
|
||||
void updateTopLevelMenu (NSMenu* menu)
|
||||
{
|
||||
NSMenu* superMenu = [menu supermenu];
|
||||
auto menuNames = currentModel->getMenuBarNames();
|
||||
auto indexOfMenu = (int) [superMenu indexOfItemWithSubmenu: menu] - 1;
|
||||
|
||||
if (indexOfMenu >= 0)
|
||||
{
|
||||
removeItemRecursive (menu);
|
||||
|
||||
auto updatedPopup = currentModel->getMenuForIndex (indexOfMenu, menuNames[indexOfMenu]);
|
||||
|
||||
for (PopupMenu::MenuItemIterator iter (updatedPopup); iter.next();)
|
||||
addMenuItem (iter, menu, 1, indexOfMenu);
|
||||
|
||||
[menu update];
|
||||
}
|
||||
}
|
||||
|
||||
void menuBarItemsChanged (MenuBarModel*) override
|
||||
{
|
||||
if (isOpen)
|
||||
{
|
||||
defferedUpdateRequested = true;
|
||||
return;
|
||||
}
|
||||
|
||||
lastUpdateTime = Time::getMillisecondCounter();
|
||||
|
||||
StringArray menuNames;
|
||||
if (currentModel != nullptr)
|
||||
menuNames = currentModel->getMenuBarNames();
|
||||
|
||||
auto* menuBar = getMainMenuBar();
|
||||
|
||||
while ([menuBar numberOfItems] > 1 + menuNames.size())
|
||||
removeItemRecursive (menuBar, static_cast<int> ([menuBar numberOfItems] - 1));
|
||||
|
||||
int menuId = 1;
|
||||
|
||||
for (int i = 0; i < menuNames.size(); ++i)
|
||||
{
|
||||
const PopupMenu menu (currentModel->getMenuForIndex (i, menuNames[i]));
|
||||
|
||||
if (i >= [menuBar numberOfItems] - 1)
|
||||
addTopLevelMenu (menuBar, menu, menuNames[i], menuId, i);
|
||||
else
|
||||
updateTopLevelMenu ([menuBar itemAtIndex: 1 + i], menu, menuNames[i], menuId, i);
|
||||
}
|
||||
}
|
||||
|
||||
void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info) override
|
||||
{
|
||||
if ((info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0
|
||||
&& info.invocationMethod != ApplicationCommandTarget::InvocationInfo::fromKeyPress)
|
||||
if (auto* item = findMenuItemWithCommandID (getMainMenuBar(), info.commandID))
|
||||
flashMenuBar ([item menu]);
|
||||
}
|
||||
|
||||
void invoke (const PopupMenu::Item& item, int topLevelIndex) const
|
||||
{
|
||||
if (currentModel != nullptr)
|
||||
{
|
||||
if (item.action != nullptr)
|
||||
{
|
||||
MessageManager::callAsync (item.action);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.customCallback != nullptr)
|
||||
if (! item.customCallback->menuItemTriggered())
|
||||
return;
|
||||
|
||||
if (item.commandManager != nullptr)
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (item.itemID);
|
||||
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
|
||||
|
||||
item.commandManager->invoke (info, true);
|
||||
}
|
||||
|
||||
MessageManager::callAsync ([=]
|
||||
{
|
||||
if (instance != nullptr)
|
||||
instance->invokeDirectly (item.itemID, topLevelIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void invokeDirectly (int commandId, int topLevelIndex)
|
||||
{
|
||||
if (currentModel != nullptr)
|
||||
currentModel->menuItemSelected (commandId, topLevelIndex);
|
||||
}
|
||||
|
||||
void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo,
|
||||
const int topLevelMenuId, const int topLevelIndex)
|
||||
{
|
||||
const PopupMenu::Item& i = iter.getItem();
|
||||
NSString* text = juceStringToNS (i.text);
|
||||
|
||||
if (text == nil)
|
||||
text = nsEmptyString();
|
||||
|
||||
if (i.isSeparator)
|
||||
{
|
||||
[menuToAddTo addItem: [NSMenuItem separatorItem]];
|
||||
}
|
||||
else if (i.isSectionHeader)
|
||||
{
|
||||
NSMenuItem* item = [menuToAddTo addItemWithTitle: text
|
||||
action: nil
|
||||
keyEquivalent: nsEmptyString()];
|
||||
|
||||
[item setEnabled: false];
|
||||
}
|
||||
else if (i.subMenu != nullptr)
|
||||
{
|
||||
if (recentItemsMenuName.isNotEmpty() && i.text == recentItemsMenuName)
|
||||
{
|
||||
if (recent == nullptr)
|
||||
recent = std::make_unique<RecentFilesMenuItem>();
|
||||
|
||||
if (recent->recentItem != nil)
|
||||
{
|
||||
if (NSMenu* parent = [recent->recentItem menu])
|
||||
[parent removeItem: recent->recentItem];
|
||||
|
||||
[menuToAddTo addItem: recent->recentItem];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSMenuItem* item = [menuToAddTo addItemWithTitle: text
|
||||
action: nil
|
||||
keyEquivalent: nsEmptyString()];
|
||||
|
||||
[item setTag: i.itemID];
|
||||
[item setEnabled: i.isEnabled];
|
||||
|
||||
NSMenu* sub = createMenu (*i.subMenu, i.text, topLevelMenuId, topLevelIndex, false);
|
||||
[menuToAddTo setSubmenu: sub forItem: item];
|
||||
[sub release];
|
||||
}
|
||||
else
|
||||
{
|
||||
auto item = [[NSMenuItem alloc] initWithTitle: text
|
||||
action: menuItemInvokedSelector
|
||||
keyEquivalent: nsEmptyString()];
|
||||
|
||||
[item setTag: topLevelIndex];
|
||||
[item setEnabled: i.isEnabled];
|
||||
[item setState: i.isTicked ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
[item setTarget: (id) callback];
|
||||
|
||||
auto* juceItem = new PopupMenu::Item (i);
|
||||
juceItem->customComponent = nullptr;
|
||||
|
||||
[item setRepresentedObject: [createNSObjectFromJuceClass (juceItem) autorelease]];
|
||||
|
||||
if (i.commandManager != nullptr)
|
||||
{
|
||||
for (auto& kp : i.commandManager->getKeyMappings()->getKeyPressesAssignedToCommand (i.itemID))
|
||||
{
|
||||
if (kp != KeyPress::backspaceKey // (adding these is annoying because it flashes the menu bar
|
||||
&& kp != KeyPress::deleteKey) // every time you press the key while editing text)
|
||||
{
|
||||
juce_wchar key = kp.getTextCharacter();
|
||||
|
||||
if (key == 0)
|
||||
key = (juce_wchar) kp.getKeyCode();
|
||||
|
||||
[item setKeyEquivalent: juceStringToNS (String::charToString (key).toLowerCase())];
|
||||
[item setKeyEquivalentModifierMask: juceModsToNSMods (kp.getModifiers())];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[menuToAddTo addItem: item];
|
||||
[item release];
|
||||
}
|
||||
}
|
||||
|
||||
NSMenu* createMenu (const PopupMenu menu,
|
||||
const String& menuName,
|
||||
const int topLevelMenuId,
|
||||
const int topLevelIndex,
|
||||
const bool addDelegate)
|
||||
{
|
||||
NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)];
|
||||
|
||||
if (addDelegate)
|
||||
[m setDelegate: (id<NSMenuDelegate>) callback];
|
||||
|
||||
for (PopupMenu::MenuItemIterator iter (menu); iter.next();)
|
||||
addMenuItem (iter, m, topLevelMenuId, topLevelIndex);
|
||||
|
||||
[m update];
|
||||
return m;
|
||||
}
|
||||
|
||||
static JuceMainMenuHandler* instance;
|
||||
|
||||
MenuBarModel* currentModel = nullptr;
|
||||
std::unique_ptr<PopupMenu> extraAppleMenuItems;
|
||||
uint32 lastUpdateTime = 0;
|
||||
NSObject* callback = nil;
|
||||
String recentItemsMenuName;
|
||||
bool isOpen = false, defferedUpdateRequested = false;
|
||||
|
||||
private:
|
||||
struct RecentFilesMenuItem
|
||||
{
|
||||
RecentFilesMenuItem() : recentItem (nil)
|
||||
{
|
||||
if (NSNib* menuNib = [[[NSNib alloc] initWithNibNamed: @"RecentFilesMenuTemplate" bundle: nil] autorelease])
|
||||
{
|
||||
NSArray* array = nil;
|
||||
|
||||
if (@available (macOS 10.11, *))
|
||||
{
|
||||
[menuNib instantiateWithOwner: NSApp
|
||||
topLevelObjects: &array];
|
||||
}
|
||||
else
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
[menuNib instantiateNibWithOwner: NSApp
|
||||
topLevelObjects: &array];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
for (id object in array)
|
||||
{
|
||||
if ([object isKindOfClass: [NSMenu class]])
|
||||
{
|
||||
if (NSArray* items = [object itemArray])
|
||||
{
|
||||
if (NSMenuItem* item = findRecentFilesItem (items))
|
||||
{
|
||||
recentItem = [item retain];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~RecentFilesMenuItem()
|
||||
{
|
||||
[recentItem release];
|
||||
}
|
||||
|
||||
static NSMenuItem* findRecentFilesItem (NSArray* const items)
|
||||
{
|
||||
for (id object in items)
|
||||
if (NSArray* subMenuItems = [[object submenu] itemArray])
|
||||
for (id subObject in subMenuItems)
|
||||
if ([subObject isKindOfClass: [NSMenuItem class]])
|
||||
return subObject;
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMenuItem* recentItem;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RecentFilesMenuItem)
|
||||
};
|
||||
|
||||
std::unique_ptr<RecentFilesMenuItem> recent;
|
||||
|
||||
//==============================================================================
|
||||
static NSMenuItem* findMenuItemWithCommandID (NSMenu* const menu, int commandID)
|
||||
{
|
||||
for (NSInteger i = [menu numberOfItems]; --i >= 0;)
|
||||
{
|
||||
NSMenuItem* m = [menu itemAtIndex: i];
|
||||
if (auto* menuItem = getJuceClassFromNSObject<PopupMenu::Item> ([m representedObject]))
|
||||
if (menuItem->itemID == commandID)
|
||||
return m;
|
||||
|
||||
if (NSMenu* sub = [m submenu])
|
||||
if (NSMenuItem* found = findMenuItemWithCommandID (sub, commandID))
|
||||
return found;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static void flashMenuBar (NSMenu* menu)
|
||||
{
|
||||
if ([[menu title] isEqualToString: nsStringLiteral ("Apple")])
|
||||
return;
|
||||
|
||||
[menu retain];
|
||||
|
||||
const unichar f35Key = NSF35FunctionKey;
|
||||
NSString* f35String = [NSString stringWithCharacters: &f35Key length: 1];
|
||||
|
||||
NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: nsStringLiteral ("x")
|
||||
action: menuItemInvokedSelector
|
||||
keyEquivalent: f35String];
|
||||
|
||||
// When the f35Event is invoked, the item's enablement is checked and a
|
||||
// NSBeep is triggered if the item appears to be disabled.
|
||||
// This ValidatorClass exists solely to return YES from validateMenuItem.
|
||||
struct ValidatorClass final : public ObjCClass<NSObject>
|
||||
{
|
||||
ValidatorClass() : ObjCClass ("JUCEMenuValidator_")
|
||||
{
|
||||
addMethod (menuItemInvokedSelector, [] (id, SEL, NSMenuItem*) {});
|
||||
addMethod (@selector (validateMenuItem:), [] (id, SEL, NSMenuItem*) { return YES; });
|
||||
addProtocol (@protocol (NSMenuItemValidation));
|
||||
|
||||
registerClass();
|
||||
}
|
||||
};
|
||||
|
||||
static ValidatorClass validatorClass;
|
||||
static auto* vcInstance = validatorClass.createInstance();
|
||||
|
||||
[item setTarget: vcInstance];
|
||||
[menu insertItem: item atIndex: [menu numberOfItems]];
|
||||
[item release];
|
||||
|
||||
if ([menu indexOfItem: item] >= 0)
|
||||
{
|
||||
NSEvent* f35Event = [NSEvent keyEventWithType: NSEventTypeKeyDown
|
||||
location: NSZeroPoint
|
||||
modifierFlags: NSEventModifierFlagCommand
|
||||
timestamp: 0
|
||||
windowNumber: 0
|
||||
context: [NSGraphicsContext currentContext]
|
||||
characters: f35String
|
||||
charactersIgnoringModifiers: f35String
|
||||
isARepeat: NO
|
||||
keyCode: 0];
|
||||
|
||||
[menu performKeyEquivalent: f35Event];
|
||||
|
||||
if ([menu indexOfItem: item] >= 0)
|
||||
[menu removeItem: item]; // (this throws if the item isn't actually in the menu)
|
||||
}
|
||||
|
||||
[menu release];
|
||||
}
|
||||
|
||||
static unsigned int juceModsToNSMods (const ModifierKeys mods)
|
||||
{
|
||||
unsigned int m = 0;
|
||||
if (mods.isShiftDown()) m |= NSEventModifierFlagShift;
|
||||
if (mods.isCtrlDown()) m |= NSEventModifierFlagControl;
|
||||
if (mods.isAltDown()) m |= NSEventModifierFlagOption;
|
||||
if (mods.isCommandDown()) m |= NSEventModifierFlagCommand;
|
||||
return m;
|
||||
}
|
||||
|
||||
// Apple Bug: For some reason [NSMenu removeAllItems] seems to leak its objects
|
||||
// on shutdown, so we need this method to release the items one-by-one manually
|
||||
static void removeItemRecursive (NSMenu* parentMenu, int menuItemIndex)
|
||||
{
|
||||
if (isPositiveAndBelow (menuItemIndex, (int) [parentMenu numberOfItems]))
|
||||
{
|
||||
if (auto menuItem = [parentMenu itemAtIndex:menuItemIndex])
|
||||
{
|
||||
if (auto submenu = [menuItem submenu])
|
||||
removeItemRecursive (submenu);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion")
|
||||
[parentMenu removeItem: menuItem];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
}
|
||||
else
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
static void removeItemRecursive (NSMenu* menu)
|
||||
{
|
||||
if (menu != nullptr)
|
||||
{
|
||||
auto n = static_cast<int> ([menu numberOfItems]);
|
||||
|
||||
for (auto i = n; --i >= 0;)
|
||||
removeItemRecursive (menu, i);
|
||||
}
|
||||
}
|
||||
|
||||
static NSMenu* getMainMenuBar()
|
||||
{
|
||||
return JuceMainMenuBarHolder::getInstance()->mainMenuBar;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct JuceMenuCallbackClass final : public ObjCClass<NSObject>
|
||||
{
|
||||
JuceMenuCallbackClass() : ObjCClass ("JUCEMainMenu_")
|
||||
{
|
||||
addIvar<JuceMainMenuHandler*> ("owner");
|
||||
|
||||
addMethod (menuItemInvokedSelector, [] (id self, SEL, NSMenuItem* item)
|
||||
{
|
||||
if (auto* juceItem = getPopupMenuItem (item))
|
||||
getOwner (self)->invoke (*juceItem, static_cast<int> ([item tag]));
|
||||
});
|
||||
|
||||
addMethod (@selector (menuNeedsUpdate:), [] (id self, SEL, NSMenu* menu)
|
||||
{
|
||||
getOwner (self)->updateTopLevelMenu (menu);
|
||||
});
|
||||
|
||||
addMethod (@selector (validateMenuItem:), [] (id, SEL, NSMenuItem* item) -> BOOL
|
||||
{
|
||||
if (auto* juceItem = getPopupMenuItem (item))
|
||||
return juceItem->isEnabled;
|
||||
|
||||
return YES;
|
||||
});
|
||||
|
||||
addProtocol (@protocol (NSMenuDelegate));
|
||||
addProtocol (@protocol (NSMenuItemValidation));
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setOwner (id self, JuceMainMenuHandler* owner)
|
||||
{
|
||||
object_setInstanceVariable (self, "owner", owner);
|
||||
}
|
||||
|
||||
private:
|
||||
static PopupMenu::Item* getPopupMenuItem (NSMenuItem* item)
|
||||
{
|
||||
return getJuceClassFromNSObject<PopupMenu::Item> ([item representedObject]);
|
||||
}
|
||||
|
||||
static JuceMainMenuHandler* getOwner (id self)
|
||||
{
|
||||
return getIvar<JuceMainMenuHandler*> (self, "owner");
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
JuceMainMenuHandler* JuceMainMenuHandler::instance = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
class TemporaryMainMenuWithStandardCommands
|
||||
{
|
||||
public:
|
||||
explicit TemporaryMainMenuWithStandardCommands (FilePreviewComponent* filePreviewComponent)
|
||||
: oldMenu (MenuBarModel::getMacMainMenu()), dummyModalComponent (filePreviewComponent)
|
||||
{
|
||||
if (auto* appleMenu = MenuBarModel::getMacExtraAppleItemsMenu())
|
||||
oldAppleMenu = std::make_unique<PopupMenu> (*appleMenu);
|
||||
|
||||
if (auto* handler = JuceMainMenuHandler::instance)
|
||||
oldRecentItems = handler->recentItemsMenuName;
|
||||
|
||||
MenuBarModel::setMacMainMenu (nullptr);
|
||||
|
||||
if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)
|
||||
{
|
||||
NSMenu* menu = [[NSMenu alloc] initWithTitle: nsStringLiteral ("Edit")];
|
||||
NSMenuItem* item;
|
||||
|
||||
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Cut"), nil)
|
||||
action: @selector (cut:) keyEquivalent: nsStringLiteral ("x")];
|
||||
[menu addItem: item];
|
||||
[item release];
|
||||
|
||||
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Copy"), nil)
|
||||
action: @selector (copy:) keyEquivalent: nsStringLiteral ("c")];
|
||||
[menu addItem: item];
|
||||
[item release];
|
||||
|
||||
item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (nsStringLiteral ("Paste"), nil)
|
||||
action: @selector (paste:) keyEquivalent: nsStringLiteral ("v")];
|
||||
[menu addItem: item];
|
||||
[item release];
|
||||
|
||||
editMenuIndex = [mainMenu numberOfItems];
|
||||
|
||||
item = [mainMenu addItemWithTitle: NSLocalizedString (nsStringLiteral ("Edit"), nil)
|
||||
action: nil keyEquivalent: nsEmptyString()];
|
||||
[mainMenu setSubmenu: menu forItem: item];
|
||||
[menu release];
|
||||
}
|
||||
|
||||
// use a dummy modal component so that apps can tell that something is currently modal.
|
||||
dummyModalComponent.enterModalState (false);
|
||||
}
|
||||
|
||||
~TemporaryMainMenuWithStandardCommands()
|
||||
{
|
||||
if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)
|
||||
[mainMenu removeItemAtIndex:editMenuIndex];
|
||||
|
||||
MenuBarModel::setMacMainMenu (oldMenu, oldAppleMenu.get(), oldRecentItems);
|
||||
}
|
||||
|
||||
static bool checkModalEvent (FilePreviewComponent* preview, const Component* targetComponent)
|
||||
{
|
||||
if (targetComponent == nullptr)
|
||||
return false;
|
||||
|
||||
return (targetComponent == preview
|
||||
|| targetComponent->findParentComponentOfClass<FilePreviewComponent>() != nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
MenuBarModel* const oldMenu = nullptr;
|
||||
std::unique_ptr<PopupMenu> oldAppleMenu;
|
||||
String oldRecentItems;
|
||||
NSInteger editMenuIndex;
|
||||
|
||||
// The OS view already plays an alert when clicking outside
|
||||
// the modal comp, so this override avoids adding extra
|
||||
// inappropriate noises when the cancel button is pressed.
|
||||
// This override is also important because it stops the base class
|
||||
// calling ModalComponentManager::bringToFront, which can get
|
||||
// recursive when file dialogs are involved
|
||||
struct SilentDummyModalComp final : public Component
|
||||
{
|
||||
explicit SilentDummyModalComp (FilePreviewComponent* p)
|
||||
: preview (p) {}
|
||||
|
||||
void inputAttemptWhenModal() override {}
|
||||
|
||||
bool canModalEventBeSentToComponent (const Component* targetComponent) override
|
||||
{
|
||||
return checkModalEvent (preview, targetComponent);
|
||||
}
|
||||
|
||||
FilePreviewComponent* preview = nullptr;
|
||||
};
|
||||
|
||||
SilentDummyModalComp dummyModalComponent;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
namespace MainMenuHelpers
|
||||
{
|
||||
static NSString* translateMenuName (const String& name)
|
||||
{
|
||||
return NSLocalizedString (juceStringToNS (TRANS (name)), nil);
|
||||
}
|
||||
|
||||
static NSMenuItem* createMenuItem (NSMenu* menu, const String& name, SEL sel, NSString* key)
|
||||
{
|
||||
NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle: translateMenuName (name)
|
||||
action: sel
|
||||
keyEquivalent: key] autorelease];
|
||||
[item setTarget: NSApp];
|
||||
[menu addItem: item];
|
||||
return item;
|
||||
}
|
||||
|
||||
static void createStandardAppMenu (NSMenu* menu, const String& appName, const PopupMenu* extraItems)
|
||||
{
|
||||
if (extraItems != nullptr && JuceMainMenuHandler::instance != nullptr && extraItems->getNumItems() > 0)
|
||||
{
|
||||
for (PopupMenu::MenuItemIterator iter (*extraItems); iter.next();)
|
||||
JuceMainMenuHandler::instance->addMenuItem (iter, menu, 0, -1);
|
||||
|
||||
[menu addItem: [NSMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
// Services...
|
||||
NSMenuItem* services = [[[NSMenuItem alloc] initWithTitle: translateMenuName ("Services")
|
||||
action: nil keyEquivalent: nsEmptyString()] autorelease];
|
||||
[menu addItem: services];
|
||||
|
||||
NSMenu* servicesMenu = [[[NSMenu alloc] initWithTitle: translateMenuName ("Services")] autorelease];
|
||||
[menu setSubmenu: servicesMenu forItem: services];
|
||||
[NSApp setServicesMenu: servicesMenu];
|
||||
[menu addItem: [NSMenuItem separatorItem]];
|
||||
|
||||
createMenuItem (menu, TRANS ("Hide") + String (" ") + appName, @selector (hide:), nsStringLiteral ("h"));
|
||||
|
||||
[createMenuItem (menu, TRANS ("Hide Others"), @selector (hideOtherApplications:), nsStringLiteral ("h"))
|
||||
setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];
|
||||
|
||||
createMenuItem (menu, TRANS ("Show All"), @selector (unhideAllApplications:), nsEmptyString());
|
||||
|
||||
[menu addItem: [NSMenuItem separatorItem]];
|
||||
|
||||
createMenuItem (menu, TRANS ("Quit") + String (" ") + appName, @selector (terminate:), nsStringLiteral ("q"));
|
||||
}
|
||||
|
||||
// Since our app has no NIB, this initialises a standard app menu...
|
||||
static void rebuildMainMenu (const PopupMenu* extraItems)
|
||||
{
|
||||
// this can't be used in a plugin!
|
||||
jassert (JUCEApplicationBase::isStandaloneApp());
|
||||
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
{
|
||||
if (auto* mainMenu = JuceMainMenuBarHolder::getInstance()->mainMenuBar)
|
||||
{
|
||||
if ([mainMenu numberOfItems] > 0)
|
||||
{
|
||||
if (auto appMenu = [[mainMenu itemAtIndex: 0] submenu])
|
||||
{
|
||||
[appMenu removeAllItems];
|
||||
MainMenuHelpers::createStandardAppMenu (appMenu, app->getApplicationName(), extraItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel,
|
||||
const PopupMenu* extraAppleMenuItems,
|
||||
const String& recentItemsMenuName)
|
||||
{
|
||||
if (getMacMainMenu() != newMenuBarModel)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
if (newMenuBarModel == nullptr)
|
||||
{
|
||||
delete JuceMainMenuHandler::instance;
|
||||
jassert (JuceMainMenuHandler::instance == nullptr); // should be zeroed in the destructor
|
||||
jassert (extraAppleMenuItems == nullptr); // you can't specify some extra items without also supplying a model
|
||||
|
||||
extraAppleMenuItems = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (JuceMainMenuHandler::instance == nullptr)
|
||||
JuceMainMenuHandler::instance = new JuceMainMenuHandler();
|
||||
|
||||
JuceMainMenuHandler::instance->setMenu (newMenuBarModel, extraAppleMenuItems, recentItemsMenuName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MainMenuHelpers::rebuildMainMenu (extraAppleMenuItems);
|
||||
|
||||
if (newMenuBarModel != nullptr)
|
||||
newMenuBarModel->menuItemsChanged();
|
||||
}
|
||||
|
||||
MenuBarModel* MenuBarModel::getMacMainMenu()
|
||||
{
|
||||
if (auto* mm = JuceMainMenuHandler::instance)
|
||||
return mm->currentModel;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const PopupMenu* MenuBarModel::getMacExtraAppleItemsMenu()
|
||||
{
|
||||
if (auto* mm = JuceMainMenuHandler::instance)
|
||||
return mm->extraAppleMenuItems.get();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
using MenuTrackingChangedCallback = void (*)(bool);
|
||||
extern MenuTrackingChangedCallback menuTrackingChangedCallback;
|
||||
|
||||
static void mainMenuTrackingChanged (bool isTracking)
|
||||
{
|
||||
PopupMenu::dismissAllActiveMenus();
|
||||
|
||||
if (auto* menuHandler = JuceMainMenuHandler::instance)
|
||||
{
|
||||
menuHandler->isOpen = isTracking;
|
||||
|
||||
if (auto* model = menuHandler->currentModel)
|
||||
model->handleMenuBarActivate (isTracking);
|
||||
|
||||
if (menuHandler->defferedUpdateRequested && ! isTracking)
|
||||
{
|
||||
menuHandler->defferedUpdateRequested = false;
|
||||
menuHandler->menuBarItemsChanged (menuHandler->currentModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void initialiseMacMainMenu()
|
||||
{
|
||||
menuTrackingChangedCallback = mainMenuTrackingChanged;
|
||||
|
||||
if (JuceMainMenuHandler::instance == nullptr)
|
||||
MainMenuHelpers::rebuildMainMenu (nullptr);
|
||||
}
|
||||
|
||||
// (used from other modules that need to create an NSMenu)
|
||||
NSMenu* createNSMenu (const PopupMenu&, const String&, int, int, bool);
|
||||
NSMenu* createNSMenu (const PopupMenu& menu, const String& name, int topLevelMenuId, int topLevelIndex, bool addDelegate)
|
||||
{
|
||||
initialiseMacMainMenu();
|
||||
|
||||
if (auto* mm = JuceMainMenuHandler::instance)
|
||||
return mm->createMenu (menu, name, topLevelMenuId, topLevelIndex, addDelegate);
|
||||
|
||||
jassertfalse; // calling this before making sure the OSX main menu stuff was initialised?
|
||||
return nil;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if JUCE_MAC
|
||||
|
||||
//==============================================================================
|
||||
class MouseCursor::PlatformSpecificHandle
|
||||
{
|
||||
public:
|
||||
PlatformSpecificHandle (const MouseCursor::StandardCursorType type)
|
||||
: cursorHandle (createCursor (type)) {}
|
||||
|
||||
PlatformSpecificHandle (const detail::CustomMouseCursorInfo& info)
|
||||
: cursorHandle (createCursor (info)) {}
|
||||
|
||||
~PlatformSpecificHandle()
|
||||
{
|
||||
[cursorHandle release];
|
||||
}
|
||||
|
||||
static void showInWindow (PlatformSpecificHandle* handle, ComponentPeer*)
|
||||
{
|
||||
auto c = [&]
|
||||
{
|
||||
if (handle == nullptr || handle->cursorHandle == nullptr)
|
||||
return [NSCursor arrowCursor];
|
||||
|
||||
return handle->cursorHandle;
|
||||
}();
|
||||
|
||||
[c set];
|
||||
}
|
||||
|
||||
private:
|
||||
static NSCursor* fromNSImage (NSImage* im, NSPoint hotspot)
|
||||
{
|
||||
NSCursor* c = [[NSCursor alloc] initWithImage: im
|
||||
hotSpot: hotspot];
|
||||
[im release];
|
||||
return c;
|
||||
}
|
||||
|
||||
static NSCursor* fromHIServices (const char* filename)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
auto cursorPath = String ("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/"
|
||||
"HIServices.framework/Versions/A/Resources/cursors/")
|
||||
+ filename;
|
||||
|
||||
NSImage* originalImage = [[NSImage alloc] initByReferencingFile: juceStringToNS (cursorPath + "/cursor.pdf")];
|
||||
NSSize originalSize = [originalImage size];
|
||||
NSImage* resultImage = [[NSImage alloc] initWithSize: originalSize];
|
||||
|
||||
for (int scale = 1; scale <= 4; ++scale)
|
||||
{
|
||||
NSAffineTransform* scaleTransform = [NSAffineTransform transform];
|
||||
[scaleTransform scaleBy: (float) scale];
|
||||
|
||||
if (CGImageRef rasterCGImage = [originalImage CGImageForProposedRect: nil
|
||||
context: nil
|
||||
hints: [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
NSImageHintCTM, scaleTransform, nil]])
|
||||
{
|
||||
NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithCGImage: rasterCGImage];
|
||||
[imageRep setSize: originalSize];
|
||||
|
||||
[resultImage addRepresentation: imageRep];
|
||||
[imageRep release];
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
[originalImage release];
|
||||
|
||||
NSDictionary* info = [NSDictionary dictionaryWithContentsOfFile: juceStringToNS (cursorPath + "/info.plist")];
|
||||
|
||||
auto hotspotX = (float) [[info valueForKey: nsStringLiteral ("hotx")] doubleValue];
|
||||
auto hotspotY = (float) [[info valueForKey: nsStringLiteral ("hoty")] doubleValue];
|
||||
|
||||
return fromNSImage (resultImage, NSMakePoint (hotspotX, hotspotY));
|
||||
}
|
||||
}
|
||||
static NSCursor* createCursor (const detail::CustomMouseCursorInfo& info)
|
||||
{
|
||||
return fromNSImage (imageToNSImage (info.image),
|
||||
NSMakePoint (info.hotspot.x, info.hotspot.y));
|
||||
}
|
||||
|
||||
static NSCursor* createCursor (const MouseCursor::StandardCursorType type)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
NSCursor* c = nil;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NormalCursor:
|
||||
case ParentCursor: c = [NSCursor arrowCursor]; break;
|
||||
case NoCursor: return createCursor ({ ScaledImage (Image (Image::ARGB, 8, 8, true)), {} });
|
||||
case DraggingHandCursor: c = [NSCursor openHandCursor]; break;
|
||||
case WaitCursor: c = [NSCursor arrowCursor]; break; // avoid this on the mac, let the OS provide the beachball
|
||||
case IBeamCursor: c = [NSCursor IBeamCursor]; break;
|
||||
case PointingHandCursor: c = [NSCursor pointingHandCursor]; break;
|
||||
case LeftEdgeResizeCursor: c = [NSCursor resizeLeftCursor]; break;
|
||||
case RightEdgeResizeCursor: c = [NSCursor resizeRightCursor]; break;
|
||||
case CrosshairCursor: c = [NSCursor crosshairCursor]; break;
|
||||
|
||||
case CopyingCursor:
|
||||
{
|
||||
c = [NSCursor dragCopyCursor];
|
||||
break;
|
||||
}
|
||||
|
||||
case UpDownResizeCursor:
|
||||
case TopEdgeResizeCursor:
|
||||
case BottomEdgeResizeCursor:
|
||||
if (NSCursor* m = fromHIServices ("resizenorthsouth"))
|
||||
return m;
|
||||
|
||||
c = [NSCursor resizeUpDownCursor];
|
||||
break;
|
||||
|
||||
case LeftRightResizeCursor:
|
||||
if (NSCursor* m = fromHIServices ("resizeeastwest"))
|
||||
return m;
|
||||
|
||||
c = [NSCursor resizeLeftRightCursor];
|
||||
break;
|
||||
|
||||
case TopLeftCornerResizeCursor:
|
||||
case BottomRightCornerResizeCursor:
|
||||
return fromHIServices ("resizenorthwestsoutheast");
|
||||
|
||||
case TopRightCornerResizeCursor:
|
||||
case BottomLeftCornerResizeCursor:
|
||||
return fromHIServices ("resizenortheastsouthwest");
|
||||
|
||||
case UpDownLeftRightResizeCursor:
|
||||
return fromHIServices ("move");
|
||||
|
||||
case NumStandardCursorTypes:
|
||||
default:
|
||||
jassertfalse;
|
||||
break;
|
||||
}
|
||||
|
||||
[c retain];
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
NSCursor* cursorHandle;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class MouseCursor::PlatformSpecificHandle
|
||||
{
|
||||
public:
|
||||
PlatformSpecificHandle (const MouseCursor::StandardCursorType) {}
|
||||
PlatformSpecificHandle (const detail::CustomMouseCursorInfo&) {}
|
||||
|
||||
static void showInWindow (PlatformSpecificHandle*, ComponentPeer*) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant")
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
template <typename IDType>
|
||||
class MultiTouchMapper
|
||||
{
|
||||
public:
|
||||
MultiTouchMapper() {}
|
||||
|
||||
int getIndexOfTouch (ComponentPeer* peer, IDType touchID)
|
||||
{
|
||||
jassert (touchID != 0); // need to rethink this if IDs can be 0!
|
||||
TouchInfo info {touchID, peer};
|
||||
|
||||
int touchIndex = currentTouches.indexOf (info);
|
||||
|
||||
if (touchIndex < 0)
|
||||
{
|
||||
auto emptyTouchIndex = currentTouches.indexOf ({});
|
||||
touchIndex = (emptyTouchIndex >= 0 ? emptyTouchIndex : currentTouches.size());
|
||||
|
||||
currentTouches.set (touchIndex, info);
|
||||
}
|
||||
|
||||
return touchIndex;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
currentTouches.clear();
|
||||
}
|
||||
|
||||
void clearTouch (int index)
|
||||
{
|
||||
currentTouches.set (index, {});
|
||||
}
|
||||
|
||||
bool areAnyTouchesActive() const noexcept
|
||||
{
|
||||
for (auto& t : currentTouches)
|
||||
if (t.touchId != 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void deleteAllTouchesForPeer (ComponentPeer* peer)
|
||||
{
|
||||
for (auto& t : currentTouches)
|
||||
if (t.owner == peer)
|
||||
t.touchId = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct TouchInfo
|
||||
{
|
||||
TouchInfo() noexcept : touchId (0), owner (nullptr) {}
|
||||
TouchInfo (IDType idToUse, ComponentPeer* peer) noexcept : touchId (idToUse), owner (peer) {}
|
||||
|
||||
TouchInfo (const TouchInfo&) = default;
|
||||
TouchInfo& operator= (const TouchInfo&) = default;
|
||||
TouchInfo (TouchInfo&&) noexcept = default;
|
||||
TouchInfo& operator= (TouchInfo&&) noexcept = default;
|
||||
|
||||
IDType touchId;
|
||||
ComponentPeer* owner;
|
||||
|
||||
bool operator== (const TouchInfo& o) const noexcept { return (touchId == o.touchId); }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
Array<TouchInfo> currentTouches;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiTouchMapper)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
+3077
File diff suppressed because it is too large
Load Diff
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
|
||||
{
|
||||
class AndroidMessageBox final : public ScopedMessageBoxInterface
|
||||
{
|
||||
public:
|
||||
explicit AndroidMessageBox (const MessageBoxOptions& o) : opts (o) {}
|
||||
|
||||
void runAsync (std::function<void (int)> recipient) override
|
||||
{
|
||||
const auto makeDialogListener = [&recipient] (int result)
|
||||
{
|
||||
return new DialogListener ([recipient, result] { recipient (result); });
|
||||
};
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> builder (env->NewObject (AndroidAlertDialogBuilder, AndroidAlertDialogBuilder.construct, getMainActivity().get()));
|
||||
|
||||
const auto setText = [&] (auto method, const String& text)
|
||||
{
|
||||
builder = LocalRef<jobject> (env->CallObjectMethod (builder, method, javaString (text).get()));
|
||||
};
|
||||
|
||||
setText (AndroidAlertDialogBuilder.setTitle, opts.getTitle());
|
||||
setText (AndroidAlertDialogBuilder.setMessage, opts.getMessage());
|
||||
builder = LocalRef<jobject> (env->CallObjectMethod (builder, AndroidAlertDialogBuilder.setCancelable, true));
|
||||
|
||||
builder = LocalRef<jobject> (env->CallObjectMethod (builder, AndroidAlertDialogBuilder.setOnCancelListener,
|
||||
CreateJavaInterface (makeDialogListener (0),
|
||||
"android/content/DialogInterface$OnCancelListener").get()));
|
||||
|
||||
const auto addButton = [&] (auto method, int index)
|
||||
{
|
||||
builder = LocalRef<jobject> (env->CallObjectMethod (builder,
|
||||
method,
|
||||
javaString (opts.getButtonText (index)).get(),
|
||||
CreateJavaInterface (makeDialogListener (index),
|
||||
"android/content/DialogInterface$OnClickListener").get()));
|
||||
};
|
||||
|
||||
addButton (AndroidAlertDialogBuilder.setPositiveButton, 0);
|
||||
|
||||
if (opts.getButtonText (1).isNotEmpty())
|
||||
addButton (AndroidAlertDialogBuilder.setNegativeButton, 1);
|
||||
|
||||
if (opts.getButtonText (2).isNotEmpty())
|
||||
addButton (AndroidAlertDialogBuilder.setNeutralButton, 2);
|
||||
|
||||
dialog = GlobalRef (LocalRef<jobject> (env->CallObjectMethod (builder, AndroidAlertDialogBuilder.create)));
|
||||
|
||||
LocalRef<jobject> window (env->CallObjectMethod (dialog, AndroidDialog.getWindow));
|
||||
|
||||
if (Desktop::getInstance().getKioskModeComponent() != nullptr)
|
||||
{
|
||||
env->CallVoidMethod (window, AndroidWindow.setFlags, FLAG_NOT_FOCUSABLE, FLAG_NOT_FOCUSABLE);
|
||||
LocalRef<jobject> decorView (env->CallObjectMethod (window, AndroidWindow.getDecorView));
|
||||
env->CallVoidMethod (decorView, AndroidView.setSystemUiVisibility, fullScreenFlags);
|
||||
}
|
||||
|
||||
env->CallVoidMethod (dialog, AndroidDialog.show);
|
||||
|
||||
if (Desktop::getInstance().getKioskModeComponent() != nullptr)
|
||||
env->CallVoidMethod (window, AndroidWindow.clearFlags, FLAG_NOT_FOCUSABLE);
|
||||
}
|
||||
|
||||
int runSync() override
|
||||
{
|
||||
// Not implemented on this platform.
|
||||
jassertfalse;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
if (dialog != nullptr)
|
||||
getEnv()->CallVoidMethod (dialog, AndroidDialogInterface.dismiss);
|
||||
}
|
||||
|
||||
private:
|
||||
const MessageBoxOptions opts;
|
||||
GlobalRef dialog;
|
||||
};
|
||||
|
||||
return std::make_unique<AndroidMessageBox> (options);
|
||||
}
|
||||
|
||||
} // namespace juce::detail
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
|
||||
{
|
||||
class MessageBox final : public ScopedMessageBoxInterface
|
||||
{
|
||||
public:
|
||||
explicit MessageBox (const MessageBoxOptions& opts) : options (opts) {}
|
||||
|
||||
void runAsync (std::function<void (int)> recipient) override
|
||||
{
|
||||
if (iOSGlobals::currentlyFocusedPeer == nullptr)
|
||||
{
|
||||
// Since iOS8, alert windows need to be associated with a window, so you need to
|
||||
// have at least one window on screen when you use this
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
alert.reset ([[UIAlertController alertControllerWithTitle: juceStringToNS (options.getTitle())
|
||||
message: juceStringToNS (options.getMessage())
|
||||
preferredStyle: UIAlertControllerStyleAlert] retain]);
|
||||
|
||||
for (auto i = 0; i < options.getNumButtons(); ++i)
|
||||
{
|
||||
const auto text = options.getButtonText (i);
|
||||
|
||||
if (text.isEmpty())
|
||||
continue;
|
||||
|
||||
auto* action = [UIAlertAction actionWithTitle: juceStringToNS (text)
|
||||
style: UIAlertActionStyleDefault
|
||||
handler: ^(UIAlertAction*)
|
||||
{
|
||||
MessageManager::callAsync ([recipient, i] { NullCheckedInvocation::invoke (recipient, i); });
|
||||
}];
|
||||
|
||||
[alert.get() addAction: action];
|
||||
|
||||
if (i == 0)
|
||||
[alert.get() setPreferredAction: action];
|
||||
}
|
||||
|
||||
[iOSGlobals::currentlyFocusedPeer->controller presentViewController: alert.get()
|
||||
animated: YES
|
||||
completion: nil];
|
||||
}
|
||||
|
||||
int runSync() override
|
||||
{
|
||||
int result = -1;
|
||||
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
runAsync ([&result] (int r) { result = r; });
|
||||
|
||||
while (result < 0)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
[[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
if (auto* alertViewController = alert.get())
|
||||
[alertViewController dismissViewControllerAnimated: YES completion: nil];
|
||||
}
|
||||
|
||||
private:
|
||||
const MessageBoxOptions options;
|
||||
NSUniquePtr<UIAlertController> alert;
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageBox)
|
||||
};
|
||||
|
||||
return std::make_unique<MessageBox> (options);
|
||||
}
|
||||
|
||||
} // namespace juce::detail
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
|
||||
{
|
||||
// On Linux, we re-use the AlertWindow rather than using a platform-specific dialog.
|
||||
// For consistency with the NativeMessageBox on other platforms, the result code must
|
||||
// match the button index, hence this adapter.
|
||||
class MessageBox final : public ScopedMessageBoxInterface
|
||||
{
|
||||
public:
|
||||
explicit MessageBox (const MessageBoxOptions& options)
|
||||
: inner (detail::AlertWindowHelpers::create (options)),
|
||||
numButtons (options.getNumButtons()) {}
|
||||
|
||||
void runAsync (std::function<void (int)> fn) override
|
||||
{
|
||||
inner->runAsync ([fn, n = numButtons] (int result)
|
||||
{
|
||||
fn (map (result, n));
|
||||
});
|
||||
}
|
||||
|
||||
int runSync() override
|
||||
{
|
||||
return map (inner->runSync(), numButtons);
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
inner->close();
|
||||
}
|
||||
|
||||
private:
|
||||
static int map (int button, int numButtons) { return (button + numButtons - 1) % numButtons; }
|
||||
|
||||
std::unique_ptr<ScopedMessageBoxInterface> inner;
|
||||
int numButtons = 0;
|
||||
};
|
||||
|
||||
return std::make_unique<MessageBox> (options);
|
||||
}
|
||||
|
||||
} // namespace juce::detail
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
|
||||
{
|
||||
class OSXMessageBox final : public ScopedMessageBoxInterface
|
||||
{
|
||||
public:
|
||||
explicit OSXMessageBox (const MessageBoxOptions& opts)
|
||||
: options (opts) {}
|
||||
|
||||
void runAsync (std::function<void (int)> recipient) override
|
||||
{
|
||||
makeAlert();
|
||||
|
||||
const auto onDone = [recipient] (NSModalResponse result)
|
||||
{
|
||||
recipient (convertResult (result));
|
||||
};
|
||||
|
||||
if (auto* comp = options.getAssociatedComponent())
|
||||
{
|
||||
if (auto* peer = comp->getPeer())
|
||||
{
|
||||
if (auto* view = static_cast<NSView*> (peer->getNativeHandle()))
|
||||
{
|
||||
if (auto* window = [view window])
|
||||
{
|
||||
if (@available (macOS 10.9, *))
|
||||
{
|
||||
[alertWindow.get() beginSheetModalForWindow: window completionHandler: ^(NSModalResponse result)
|
||||
{
|
||||
onDone (result);
|
||||
}];
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto result = [alertWindow.get() runModal];
|
||||
onDone (result);
|
||||
}
|
||||
|
||||
int runSync() override
|
||||
{
|
||||
makeAlert();
|
||||
return convertResult ([alertWindow.get() runModal]);
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
if (auto* alert = alertWindow.get())
|
||||
[[alert window] close];
|
||||
}
|
||||
|
||||
private:
|
||||
static int convertResult (NSModalResponse response)
|
||||
{
|
||||
switch (response)
|
||||
{
|
||||
case NSAlertFirstButtonReturn: return 0;
|
||||
case NSAlertSecondButtonReturn: return 1;
|
||||
case NSAlertThirdButtonReturn: return 2;
|
||||
default: break;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void addButton (NSAlert* alert, const String& button)
|
||||
{
|
||||
if (! button.isEmpty())
|
||||
[alert addButtonWithTitle: juceStringToNS (button)];
|
||||
}
|
||||
|
||||
void makeAlert()
|
||||
{
|
||||
NSAlert* alert = [[NSAlert alloc] init];
|
||||
|
||||
[alert setMessageText: juceStringToNS (options.getTitle())];
|
||||
[alert setInformativeText: juceStringToNS (options.getMessage())];
|
||||
|
||||
[alert setAlertStyle: options.getIconType() == MessageBoxIconType::WarningIcon ? NSAlertStyleCritical
|
||||
: NSAlertStyleInformational];
|
||||
|
||||
const auto button1Text = options.getButtonText (0);
|
||||
|
||||
addButton (alert, button1Text.isEmpty() ? "OK" : button1Text);
|
||||
addButton (alert, options.getButtonText (1));
|
||||
addButton (alert, options.getButtonText (2));
|
||||
|
||||
alertWindow.reset (alert);
|
||||
}
|
||||
|
||||
NSUniquePtr<NSAlert> alertWindow;
|
||||
MessageBoxOptions options;
|
||||
std::unique_ptr<ModalComponentManager::Callback> callback;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXMessageBox)
|
||||
};
|
||||
|
||||
return std::make_unique<OSXMessageBox> (options);
|
||||
}
|
||||
|
||||
} // namespace juce::detail
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
#if JUCE_MSVC
|
||||
// required to enable the newer dialog box on vista and above
|
||||
#pragma comment(linker, \
|
||||
"\"/MANIFESTDEPENDENCY:type='Win32' " \
|
||||
"name='Microsoft.Windows.Common-Controls' " \
|
||||
"version='6.0.0.0' " \
|
||||
"processorArchitecture='*' " \
|
||||
"publicKeyToken='6595b64144ccf1df' " \
|
||||
"language='*'\"" \
|
||||
)
|
||||
#endif
|
||||
|
||||
std::unique_ptr<ScopedMessageBoxInterface> ScopedMessageBoxInterface::create (const MessageBoxOptions& options)
|
||||
{
|
||||
class WindowsMessageBoxBase : public ScopedMessageBoxInterface
|
||||
{
|
||||
public:
|
||||
explicit WindowsMessageBoxBase (Component* comp)
|
||||
: associatedComponent (comp) {}
|
||||
|
||||
void runAsync (std::function<void (int)> recipient) override
|
||||
{
|
||||
future = std::async (std::launch::async, [showMessageBox = getShowMessageBox(), recipient]
|
||||
{
|
||||
const auto initComResult = CoInitializeEx (nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
|
||||
if (initComResult != S_OK)
|
||||
return;
|
||||
|
||||
const ScopeGuard scope { [] { CoUninitialize(); } };
|
||||
|
||||
const auto messageResult = showMessageBox != nullptr ? showMessageBox() : 0;
|
||||
NullCheckedInvocation::invoke (recipient, messageResult);
|
||||
});
|
||||
}
|
||||
|
||||
int runSync() override
|
||||
{
|
||||
if (auto showMessageBox = getShowMessageBox())
|
||||
return showMessageBox();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
if (auto* toClose = windowHandle.exchange (nullptr))
|
||||
EndDialog (toClose, 0);
|
||||
}
|
||||
|
||||
void setDialogWindowHandle (HWND dialogHandle)
|
||||
{
|
||||
windowHandle = dialogHandle;
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<int()> getShowMessageBox()
|
||||
{
|
||||
const auto parent = associatedComponent != nullptr ? (HWND) associatedComponent->getWindowHandle() : nullptr;
|
||||
return getShowMessageBoxForParent (parent);
|
||||
}
|
||||
|
||||
/* Returns a function that should display a message box and return the result.
|
||||
|
||||
getShowMessageBoxForParent() will be called on the message thread.
|
||||
|
||||
The returned function will be called on a separate thread, in order to avoid blocking the
|
||||
message thread.
|
||||
|
||||
'this' is guaranteed to be alive when the returned function is called.
|
||||
*/
|
||||
virtual std::function<int()> getShowMessageBoxForParent (HWND parent) = 0;
|
||||
|
||||
Component::SafePointer<Component> associatedComponent;
|
||||
std::atomic<HWND> windowHandle { nullptr };
|
||||
std::future<void> future;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMessageBoxBase)
|
||||
};
|
||||
|
||||
class PreVistaMessageBox final : public WindowsMessageBoxBase
|
||||
{
|
||||
public:
|
||||
PreVistaMessageBox (const MessageBoxOptions& opts, UINT extraFlags)
|
||||
: WindowsMessageBoxBase (opts.getAssociatedComponent()),
|
||||
flags (extraFlags | getMessageBoxFlags (opts.getIconType())),
|
||||
title (opts.getTitle()), message (opts.getMessage()) {}
|
||||
|
||||
private:
|
||||
std::function<int()> getShowMessageBoxForParent (const HWND parent) override
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
|
||||
static std::map<DWORD, PreVistaMessageBox*> map;
|
||||
static std::mutex mapMutex;
|
||||
|
||||
return [this, parent]
|
||||
{
|
||||
const auto threadId = GetCurrentThreadId();
|
||||
|
||||
{
|
||||
const std::scoped_lock scope { mapMutex };
|
||||
map.emplace (threadId, this);
|
||||
}
|
||||
|
||||
const ScopeGuard eraseFromMap { [threadId]
|
||||
{
|
||||
const std::scoped_lock scope { mapMutex };
|
||||
map.erase (threadId);
|
||||
} };
|
||||
|
||||
const auto hookCallback = [] (int nCode, const WPARAM wParam, const LPARAM lParam)
|
||||
{
|
||||
auto* params = reinterpret_cast<CWPSTRUCT*> (lParam);
|
||||
|
||||
if (nCode >= 0
|
||||
&& params != nullptr
|
||||
&& (params->message == WM_INITDIALOG || params->message == WM_DESTROY))
|
||||
{
|
||||
const auto callbackThreadId = GetCurrentThreadId();
|
||||
|
||||
const std::scoped_lock scope { mapMutex };
|
||||
|
||||
if (const auto iter = map.find (callbackThreadId); iter != map.cend())
|
||||
iter->second->setDialogWindowHandle (params->message == WM_INITDIALOG ? params->hwnd : nullptr);
|
||||
}
|
||||
|
||||
return CallNextHookEx ({}, nCode, wParam, lParam);
|
||||
};
|
||||
|
||||
const auto hook = SetWindowsHookEx (WH_CALLWNDPROC,
|
||||
hookCallback,
|
||||
(HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(),
|
||||
threadId);
|
||||
const ScopeGuard removeHook { [hook] { UnhookWindowsHookEx (hook); } };
|
||||
|
||||
const auto result = MessageBox (parent, message.toWideCharPointer(), title.toWideCharPointer(), flags);
|
||||
|
||||
if (result == IDYES || result == IDOK) return 0;
|
||||
if (result == IDNO && ((flags & 1) != 0)) return 1;
|
||||
|
||||
return 2;
|
||||
};
|
||||
}
|
||||
|
||||
static UINT getMessageBoxFlags (MessageBoxIconType iconType) noexcept
|
||||
{
|
||||
// this window can get lost behind JUCE windows which are set to be alwaysOnTop
|
||||
// so if there are any set it to be topmost
|
||||
const auto topmostFlag = WindowUtils::areThereAnyAlwaysOnTopWindows() ? MB_TOPMOST : 0;
|
||||
|
||||
const auto iconFlags = [&]() -> decltype (topmostFlag)
|
||||
{
|
||||
switch (iconType)
|
||||
{
|
||||
case MessageBoxIconType::QuestionIcon: return MB_ICONQUESTION;
|
||||
case MessageBoxIconType::WarningIcon: return MB_ICONWARNING;
|
||||
case MessageBoxIconType::InfoIcon: return MB_ICONINFORMATION;
|
||||
case MessageBoxIconType::NoIcon: break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}();
|
||||
|
||||
return static_cast<UINT> (MB_TASKMODAL | MB_SETFOREGROUND | topmostFlag | iconFlags);
|
||||
}
|
||||
|
||||
const UINT flags;
|
||||
const String title, message;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreVistaMessageBox)
|
||||
};
|
||||
|
||||
class WindowsTaskDialog final : public WindowsMessageBoxBase
|
||||
{
|
||||
static auto getTaskDialogFunc()
|
||||
{
|
||||
using TaskDialogIndirectFunc = HRESULT (WINAPI*) (const TASKDIALOGCONFIG*, INT*, INT*, BOOL*);
|
||||
|
||||
static const auto result = [&]() -> TaskDialogIndirectFunc
|
||||
{
|
||||
if (SystemStats::getOperatingSystemType() < SystemStats::WinVista)
|
||||
return nullptr;
|
||||
|
||||
const auto comctl = "Comctl32.dll";
|
||||
LoadLibraryA (comctl);
|
||||
const auto comctlModule = GetModuleHandleA (comctl);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type")
|
||||
if (comctlModule != nullptr)
|
||||
return (TaskDialogIndirectFunc) GetProcAddress (comctlModule, "TaskDialogIndirect");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit WindowsTaskDialog (const MessageBoxOptions& opts)
|
||||
: WindowsMessageBoxBase (opts.getAssociatedComponent()),
|
||||
iconType (opts.getIconType()),
|
||||
title (opts.getTitle()), message (opts.getMessage()),
|
||||
buttons { opts.getButtonText (0), opts.getButtonText (1), opts.getButtonText (2) } {}
|
||||
|
||||
static bool isAvailable()
|
||||
{
|
||||
return getTaskDialogFunc() != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<int()> getShowMessageBoxForParent (const HWND parent) override
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
|
||||
return [this, parent]
|
||||
{
|
||||
TASKDIALOGCONFIG config{};
|
||||
|
||||
config.cbSize = sizeof (config);
|
||||
config.hwndParent = parent;
|
||||
config.pszWindowTitle = title.toWideCharPointer();
|
||||
config.pszContent = message.toWideCharPointer();
|
||||
config.hInstance = (HINSTANCE) Process::getCurrentModuleInstanceHandle();
|
||||
config.lpCallbackData = reinterpret_cast<LONG_PTR> (this);
|
||||
config.pfCallback = [] (HWND hwnd, UINT msg, WPARAM, LPARAM, LONG_PTR lpRefData)
|
||||
{
|
||||
if (auto* t = reinterpret_cast<WindowsTaskDialog*> (lpRefData))
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case TDN_CREATED:
|
||||
case TDN_DIALOG_CONSTRUCTED:
|
||||
t->setDialogWindowHandle (hwnd);
|
||||
break;
|
||||
|
||||
case TDN_DESTROYED:
|
||||
t->setDialogWindowHandle (nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
};
|
||||
|
||||
if (iconType == MessageBoxIconType::QuestionIcon)
|
||||
{
|
||||
if (auto* questionIcon = LoadIcon (nullptr, IDI_QUESTION))
|
||||
{
|
||||
config.hMainIcon = questionIcon;
|
||||
config.dwFlags |= TDF_USE_HICON_MAIN;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
config.pszMainIcon = [&]() -> LPWSTR
|
||||
{
|
||||
switch (iconType)
|
||||
{
|
||||
case MessageBoxIconType::WarningIcon: return TD_WARNING_ICON;
|
||||
case MessageBoxIconType::InfoIcon: return TD_INFORMATION_ICON;
|
||||
|
||||
case MessageBoxIconType::QuestionIcon: JUCE_FALLTHROUGH
|
||||
case MessageBoxIconType::NoIcon:
|
||||
break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}();
|
||||
}
|
||||
|
||||
std::vector<TASKDIALOG_BUTTON> buttonLabels;
|
||||
|
||||
for (const auto& buttonText : buttons)
|
||||
if (buttonText.isNotEmpty())
|
||||
buttonLabels.push_back ({ (int) buttonLabels.size(), buttonText.toWideCharPointer() });
|
||||
|
||||
config.pButtons = buttonLabels.data();
|
||||
config.cButtons = (UINT) buttonLabels.size();
|
||||
|
||||
int buttonIndex = 0;
|
||||
|
||||
if (auto* func = getTaskDialogFunc())
|
||||
func (&config, &buttonIndex, nullptr, nullptr);
|
||||
else
|
||||
jassertfalse;
|
||||
|
||||
return buttonIndex;
|
||||
};
|
||||
}
|
||||
|
||||
const MessageBoxIconType iconType;
|
||||
const String title, message;
|
||||
const std::array<String, 3> buttons;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsTaskDialog)
|
||||
};
|
||||
|
||||
if (WindowsTaskDialog::isAvailable())
|
||||
return std::make_unique<WindowsTaskDialog> (options);
|
||||
|
||||
const auto extraFlags = [&options]
|
||||
{
|
||||
const auto numButtons = options.getNumButtons();
|
||||
|
||||
if (numButtons == 3)
|
||||
return MB_YESNOCANCEL;
|
||||
|
||||
if (numButtons == 2)
|
||||
return options.getButtonText (0) == "OK" ? MB_OKCANCEL
|
||||
: MB_YESNO;
|
||||
|
||||
return MB_OK;
|
||||
}();
|
||||
|
||||
return std::make_unique<PreVistaMessageBox> (options, (UINT) extraFlags);
|
||||
}
|
||||
|
||||
} // namespace juce::detail
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
/**
|
||||
Sets up a native control to be hosted on top of a JUCE component.
|
||||
*/
|
||||
class NativeModalWrapperComponent : public Component
|
||||
{
|
||||
public:
|
||||
void parentHierarchyChanged() final
|
||||
{
|
||||
auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
|
||||
|
||||
if (std::exchange (peer, newPeer) == newPeer)
|
||||
return;
|
||||
|
||||
if (peer == nullptr)
|
||||
return;
|
||||
|
||||
if (isIPad())
|
||||
{
|
||||
getViewController().preferredContentSize = peer->view.frame.size;
|
||||
|
||||
if (auto* popoverController = getViewController().popoverPresentationController)
|
||||
{
|
||||
popoverController.sourceView = peer->view;
|
||||
popoverController.sourceRect = CGRectMake (0.0f, (float) getHeight() - 10.0f, (float) getWidth(), 10.0f);
|
||||
popoverController.canOverlapSourceViewRect = YES;
|
||||
popoverController.delegate = popoverDelegate.get();
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* parentController = peer->controller)
|
||||
[parentController showViewController: getViewController() sender: parentController];
|
||||
|
||||
peer->toFront (false);
|
||||
}
|
||||
|
||||
void displayNativeWindowModally (Component* parent)
|
||||
{
|
||||
setOpaque (false);
|
||||
|
||||
if (parent != nullptr)
|
||||
{
|
||||
[getViewController() setModalPresentationStyle: UIModalPresentationPageSheet];
|
||||
|
||||
setBounds (parent->getLocalBounds());
|
||||
|
||||
setAlwaysOnTop (true);
|
||||
parent->addAndMakeVisible (this);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SystemStats::isRunningInAppExtensionSandbox())
|
||||
{
|
||||
// Opening a native top-level window in an AUv3 is not allowed (sandboxing). You need to specify a
|
||||
// parent component (for example your editor) to parent the native file chooser window. To do this
|
||||
// specify a parent component in the FileChooser's constructor!
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
auto chooserBounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
|
||||
setBounds (chooserBounds);
|
||||
|
||||
setAlwaysOnTop (true);
|
||||
setVisible (true);
|
||||
addToDesktop (0);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
virtual UIViewController* getViewController() const = 0;
|
||||
|
||||
static bool isIPad()
|
||||
{
|
||||
return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
|
||||
}
|
||||
|
||||
struct PopoverDelegateClass : public ObjCClass<NSObject<UIPopoverPresentationControllerDelegate>>
|
||||
{
|
||||
PopoverDelegateClass()
|
||||
: ObjCClass ("PopoverDelegateClass_")
|
||||
{
|
||||
addMethod (@selector (popoverPresentationController:willRepositionPopoverToRect:inView:), [] (id, SEL, UIPopoverPresentationController*, CGRect* rect, UIView*)
|
||||
{
|
||||
auto screenBounds = [UIScreen mainScreen].bounds;
|
||||
|
||||
rect->origin.x = 0.f;
|
||||
rect->origin.y = screenBounds.size.height - 10.f;
|
||||
rect->size.width = screenBounds.size.width;
|
||||
rect->size.height = 10.f;
|
||||
});
|
||||
|
||||
registerClass();
|
||||
}
|
||||
};
|
||||
|
||||
UIViewComponentPeer* peer = nullptr;
|
||||
NSUniquePtr<NSObject<UIPopoverPresentationControllerDelegate>> popoverDelegate { []
|
||||
{
|
||||
static PopoverDelegateClass cls;
|
||||
return cls.createInstance();
|
||||
}() };
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/*
|
||||
Forwards NSNotificationCenter callbacks to a std::function<void()>.
|
||||
*/
|
||||
class FunctionNotificationCenterObserver
|
||||
{
|
||||
public:
|
||||
FunctionNotificationCenterObserver (NSNotificationName notificationName,
|
||||
id objectToObserve,
|
||||
std::function<void()> callback)
|
||||
: onNotification (std::move (callback)),
|
||||
observer (observerObject.get(), getSelector(), notificationName, objectToObserve)
|
||||
{}
|
||||
|
||||
private:
|
||||
struct ObserverClass
|
||||
{
|
||||
ObserverClass()
|
||||
{
|
||||
klass.addIvar<FunctionNotificationCenterObserver*> ("owner");
|
||||
|
||||
klass.addMethod (getSelector(), [] (id self, SEL, NSNotification*)
|
||||
{
|
||||
getIvar<FunctionNotificationCenterObserver*> (self, "owner")->onNotification();
|
||||
});
|
||||
|
||||
klass.registerClass();
|
||||
}
|
||||
|
||||
NSObject* createInstance() const { return klass.createInstance(); }
|
||||
|
||||
private:
|
||||
ObjCClass<NSObject> klass { "JUCEObserverClass_" };
|
||||
};
|
||||
|
||||
static SEL getSelector()
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
return @selector (notificationFired:);
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
std::function<void()> onNotification;
|
||||
|
||||
NSUniquePtr<NSObject> observerObject
|
||||
{
|
||||
[this]
|
||||
{
|
||||
static ObserverClass observerClass;
|
||||
auto* result = observerClass.createInstance();
|
||||
object_setInstanceVariable (result, "owner", this);
|
||||
return result;
|
||||
}()
|
||||
};
|
||||
|
||||
ScopedNotificationCenterObserver observer;
|
||||
|
||||
// Instances can't be copied or moved, because 'this' is stored as a member of the ObserverClass
|
||||
// object.
|
||||
JUCE_DECLARE_NON_COPYABLE (FunctionNotificationCenterObserver)
|
||||
JUCE_DECLARE_NON_MOVEABLE (FunctionNotificationCenterObserver)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/*
|
||||
Manages the lifetime of a CVDisplayLinkRef for a single display, and automatically starts and
|
||||
stops it.
|
||||
*/
|
||||
class ScopedDisplayLink
|
||||
{
|
||||
public:
|
||||
static CGDirectDisplayID getDisplayIdForScreen (NSScreen* screen)
|
||||
{
|
||||
return (CGDirectDisplayID) [[screen.deviceDescription objectForKey: @"NSScreenNumber"] unsignedIntegerValue];
|
||||
}
|
||||
|
||||
ScopedDisplayLink (NSScreen* screenIn, std::function<void()> onCallbackIn)
|
||||
: displayId (getDisplayIdForScreen (screenIn)),
|
||||
link ([display = displayId]
|
||||
{
|
||||
CVDisplayLinkRef ptr = nullptr;
|
||||
[[maybe_unused]] const auto result = CVDisplayLinkCreateWithCGDisplay (display, &ptr);
|
||||
jassert (result == kCVReturnSuccess);
|
||||
jassert (ptr != nullptr);
|
||||
return ptr;
|
||||
}()),
|
||||
onCallback (std::move (onCallbackIn))
|
||||
{
|
||||
const auto callback = [] (CVDisplayLinkRef,
|
||||
const CVTimeStamp*,
|
||||
const CVTimeStamp*,
|
||||
CVOptionFlags,
|
||||
CVOptionFlags*,
|
||||
void* context) -> int
|
||||
{
|
||||
static_cast<const ScopedDisplayLink*> (context)->onCallback();
|
||||
return kCVReturnSuccess;
|
||||
};
|
||||
|
||||
[[maybe_unused]] const auto callbackResult = CVDisplayLinkSetOutputCallback (link.get(), callback, this);
|
||||
jassert (callbackResult == kCVReturnSuccess);
|
||||
|
||||
[[maybe_unused]] const auto startResult = CVDisplayLinkStart (link.get());
|
||||
jassert (startResult == kCVReturnSuccess);
|
||||
}
|
||||
|
||||
~ScopedDisplayLink() noexcept
|
||||
{
|
||||
if (link != nullptr)
|
||||
CVDisplayLinkStop (link.get());
|
||||
}
|
||||
|
||||
CGDirectDisplayID getDisplayId() const { return displayId; }
|
||||
|
||||
double getNominalVideoRefreshPeriodS() const
|
||||
{
|
||||
const auto nominalVideoRefreshPeriod = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (link.get());
|
||||
|
||||
if ((nominalVideoRefreshPeriod.flags & kCVTimeIsIndefinite) == 0)
|
||||
return (double) nominalVideoRefreshPeriod.timeValue / (double) nominalVideoRefreshPeriod.timeScale;
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private:
|
||||
struct DisplayLinkDestructor
|
||||
{
|
||||
void operator() (CVDisplayLinkRef ptr) const
|
||||
{
|
||||
if (ptr != nullptr)
|
||||
CVDisplayLinkRelease (ptr);
|
||||
}
|
||||
};
|
||||
|
||||
CGDirectDisplayID displayId;
|
||||
std::unique_ptr<std::remove_pointer_t<CVDisplayLinkRef>, DisplayLinkDestructor> link;
|
||||
std::function<void()> onCallback;
|
||||
|
||||
// Instances can't be copied or moved, because 'this' is passed as context to
|
||||
// CVDisplayLinkSetOutputCallback
|
||||
JUCE_DECLARE_NON_COPYABLE (ScopedDisplayLink)
|
||||
JUCE_DECLARE_NON_MOVEABLE (ScopedDisplayLink)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/*
|
||||
Holds a ScopedDisplayLink for each screen. When the screen configuration changes, the
|
||||
ScopedDisplayLinks will be recreated automatically to match the new configuration.
|
||||
*/
|
||||
class PerScreenDisplayLinks
|
||||
{
|
||||
public:
|
||||
PerScreenDisplayLinks()
|
||||
{
|
||||
refreshScreens();
|
||||
}
|
||||
|
||||
using RefreshCallback = std::function<void()>;
|
||||
using Factory = std::function<RefreshCallback (CGDirectDisplayID)>;
|
||||
|
||||
/*
|
||||
Automatically unregisters a CVDisplayLink callback factory when ~Connection() is called.
|
||||
*/
|
||||
class Connection
|
||||
{
|
||||
public:
|
||||
Connection() = default;
|
||||
|
||||
Connection (PerScreenDisplayLinks& linksIn, std::list<Factory>::const_iterator it)
|
||||
: links (&linksIn), iter (it) {}
|
||||
|
||||
~Connection() noexcept
|
||||
{
|
||||
if (links != nullptr)
|
||||
links->unregisterFactory (iter);
|
||||
}
|
||||
|
||||
Connection (const Connection&) = delete;
|
||||
Connection& operator= (const Connection&) = delete;
|
||||
|
||||
Connection (Connection&& other) noexcept
|
||||
: links (std::exchange (other.links, nullptr)), iter (other.iter) {}
|
||||
|
||||
Connection& operator= (Connection&& other) noexcept
|
||||
{
|
||||
Connection { std::move (other) }.swap (*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
void swap (Connection& other) noexcept
|
||||
{
|
||||
std::swap (other.links, links);
|
||||
std::swap (other.iter, iter);
|
||||
}
|
||||
|
||||
PerScreenDisplayLinks* links = nullptr;
|
||||
std::list<Factory>::const_iterator iter;
|
||||
};
|
||||
|
||||
/* Stores the provided factory for as long as the returned Connection remains alive.
|
||||
|
||||
Whenever the screen configuration changes, the factory function will be called for each
|
||||
screen. The RefreshCallback returned by the factory will be called every time that screen's
|
||||
display link callback fires.
|
||||
*/
|
||||
[[nodiscard]] Connection registerFactory (Factory factory)
|
||||
{
|
||||
const ScopedLock lock (mutex);
|
||||
factories.push_front (std::move (factory));
|
||||
refreshScreens();
|
||||
return { *this, factories.begin() };
|
||||
}
|
||||
|
||||
double getNominalVideoRefreshPeriodSForScreen (CGDirectDisplayID display) const
|
||||
{
|
||||
const ScopedLock lock (mutex);
|
||||
|
||||
for (const auto& link : links)
|
||||
if (link.getDisplayId() == display)
|
||||
return link.getNominalVideoRefreshPeriodS();
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private:
|
||||
void unregisterFactory (std::list<Factory>::const_iterator iter)
|
||||
{
|
||||
const ScopedLock lock (mutex);
|
||||
factories.erase (iter);
|
||||
refreshScreens();
|
||||
}
|
||||
|
||||
void refreshScreens()
|
||||
{
|
||||
auto newLinks = [&]
|
||||
{
|
||||
std::list<ScopedDisplayLink> result;
|
||||
|
||||
for (NSScreen* screen in [NSScreen screens])
|
||||
{
|
||||
std::vector<RefreshCallback> callbacks;
|
||||
|
||||
for (auto& factory : factories)
|
||||
callbacks.push_back (factory (ScopedDisplayLink::getDisplayIdForScreen (screen)));
|
||||
|
||||
// This is the callback that will actually fire in response to this screen's display
|
||||
// link callback.
|
||||
result.emplace_back (screen, [cbs = std::move (callbacks)]
|
||||
{
|
||||
for (const auto& callback : cbs)
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}();
|
||||
|
||||
const ScopedLock lock (mutex);
|
||||
links = std::move (newLinks);
|
||||
}
|
||||
|
||||
CriticalSection mutex;
|
||||
// This is a list rather than a vector so that the iterators are stable, even when items are
|
||||
// added/removed from the list. This is important because Connection objects store an iterator
|
||||
// internally, and may be created/destroyed arbitrarily.
|
||||
std::list<Factory> factories;
|
||||
// This is a list rather than a vector because ScopedDisplayLink is non-moveable.
|
||||
std::list<ScopedDisplayLink> links;
|
||||
|
||||
FunctionNotificationCenterObserver screenParamsObserver { NSApplicationDidChangeScreenParametersNotification,
|
||||
nullptr,
|
||||
[this] { refreshScreens(); } };
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if ! JUCE_WINDOWS
|
||||
ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() { ignoreUnused (previousContext); }
|
||||
ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() {}
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A Windows-specific class that temporarily sets the DPI awareness context of
|
||||
the current thread to be DPI unaware and resets it to the previous context
|
||||
when it goes out of scope.
|
||||
|
||||
If you create one of these before creating a top-level window, the window
|
||||
will be DPI unaware and bitmap stretched by the OS on a display with >100%
|
||||
scaling.
|
||||
|
||||
You shouldn't use this unless you really know what you are doing and
|
||||
are dealing with native HWNDs.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ScopedDPIAwarenessDisabler
|
||||
{
|
||||
public:
|
||||
ScopedDPIAwarenessDisabler();
|
||||
~ScopedDPIAwarenessDisabler();
|
||||
|
||||
private:
|
||||
void* previousContext = nullptr;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class ScopedThreadDPIAwarenessSetter
|
||||
{
|
||||
public:
|
||||
explicit ScopedThreadDPIAwarenessSetter (void* nativeWindow);
|
||||
~ScopedThreadDPIAwarenessSetter();
|
||||
|
||||
private:
|
||||
class NativeImpl;
|
||||
std::unique_ptr<NativeImpl> pimpl;
|
||||
|
||||
JUCE_LEAK_DETECTOR (ScopedThreadDPIAwarenessSetter)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
extern XContext windowHandleXContext;
|
||||
|
||||
/* Attaches a pointer to a given window, so that it can be retrieved with XFindContext on
|
||||
the windowHandleXContext.
|
||||
*/
|
||||
class ScopedWindowAssociation
|
||||
{
|
||||
public:
|
||||
ScopedWindowAssociation() = default;
|
||||
|
||||
ScopedWindowAssociation (void* associatedIn, Window windowIn)
|
||||
: associatedPointer ([&]() -> void*
|
||||
{
|
||||
if (associatedIn == nullptr)
|
||||
return nullptr;
|
||||
|
||||
// If you hit this, there's already a pointer associated with this window.
|
||||
const auto display = XWindowSystem::getInstance()->getDisplay();
|
||||
jassert (! getAssociatedPointer (display, windowIn).has_value());
|
||||
|
||||
if (X11Symbols::getInstance()->xSaveContext (display,
|
||||
static_cast<XID> (windowIn),
|
||||
windowHandleXContext,
|
||||
unalignedPointerCast<XPointer> (associatedIn)) != 0)
|
||||
{
|
||||
jassertfalse;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return associatedIn;
|
||||
}()),
|
||||
window (static_cast<XID> (windowIn)) {}
|
||||
|
||||
ScopedWindowAssociation (const ScopedWindowAssociation&) = delete;
|
||||
ScopedWindowAssociation& operator= (const ScopedWindowAssociation&) = delete;
|
||||
|
||||
ScopedWindowAssociation (ScopedWindowAssociation&& other) noexcept
|
||||
: associatedPointer (std::exchange (other.associatedPointer, nullptr)), window (other.window) {}
|
||||
|
||||
ScopedWindowAssociation& operator= (ScopedWindowAssociation&& other) noexcept
|
||||
{
|
||||
ScopedWindowAssociation { std::move (other) }.swap (*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~ScopedWindowAssociation() noexcept
|
||||
{
|
||||
if (associatedPointer == nullptr)
|
||||
return;
|
||||
|
||||
const auto display = XWindowSystem::getInstance()->getDisplay();
|
||||
const auto ptr = getAssociatedPointer (display, window);
|
||||
|
||||
if (! ptr.has_value())
|
||||
{
|
||||
// If you hit this, something else has cleared this association before we were able to.
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
jassert (unalignedPointerCast<XPointer> (associatedPointer) == *ptr);
|
||||
|
||||
if (X11Symbols::getInstance()->xDeleteContext (display, window, windowHandleXContext) != 0)
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
bool isValid() const { return associatedPointer != nullptr; }
|
||||
|
||||
private:
|
||||
static std::optional<XPointer> getAssociatedPointer (Display* display, Window window)
|
||||
{
|
||||
XPointer ptr{};
|
||||
|
||||
if (X11Symbols::getInstance()->xFindContext (display, window, windowHandleXContext, &ptr) != 0)
|
||||
return std::nullopt;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void swap (ScopedWindowAssociation& other) noexcept
|
||||
{
|
||||
std::swap (other.associatedPointer, associatedPointer);
|
||||
std::swap (other.window, window);
|
||||
}
|
||||
|
||||
void* associatedPointer = nullptr;
|
||||
XID window{};
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+2416
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
bool WindowUtils::areThereAnyAlwaysOnTopWindows()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
bool WindowUtils::areThereAnyAlwaysOnTopWindows()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct WindowUtilsInternal
|
||||
{
|
||||
inline static int numAlwaysOnTopPeers = 0;
|
||||
};
|
||||
|
||||
bool WindowUtils::areThereAnyAlwaysOnTopWindows()
|
||||
{
|
||||
return WindowUtilsInternal::numAlwaysOnTopPeers > 0;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
bool WindowUtils::areThereAnyAlwaysOnTopWindows()
|
||||
{
|
||||
for (NSWindow* window in [NSApp windows])
|
||||
if ([window level] > NSNormalWindowLevel)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
static BOOL CALLBACK enumAlwaysOnTopWindows (HWND hwnd, LPARAM lParam)
|
||||
{
|
||||
if (IsWindowVisible (hwnd))
|
||||
{
|
||||
DWORD processID = 0;
|
||||
GetWindowThreadProcessId (hwnd, &processID);
|
||||
|
||||
if (processID == GetCurrentProcessId())
|
||||
{
|
||||
WINDOWINFO info{};
|
||||
|
||||
if (GetWindowInfo (hwnd, &info)
|
||||
&& (info.dwExStyle & WS_EX_TOPMOST) != 0)
|
||||
{
|
||||
*reinterpret_cast<bool*> (lParam) = true;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool WindowUtils::areThereAnyAlwaysOnTopWindows()
|
||||
{
|
||||
bool anyAlwaysOnTopFound = false;
|
||||
EnumWindows (&enumAlwaysOnTopWindows, (LPARAM) &anyAlwaysOnTopFound);
|
||||
return anyAlwaysOnTopFound;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,741 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
extern bool isIOSAppActive;
|
||||
|
||||
struct AppInactivityCallback // NB: careful, this declaration is duplicated in other modules
|
||||
{
|
||||
virtual ~AppInactivityCallback() = default;
|
||||
virtual void appBecomingInactive() = 0;
|
||||
};
|
||||
|
||||
// This is an internal list of callbacks (but currently used between modules)
|
||||
Array<AppInactivityCallback*> appBecomingInactiveCallbacks;
|
||||
} // namespace juce
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
@interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate, UNUserNotificationCenterDelegate>
|
||||
#else
|
||||
@interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate>
|
||||
#endif
|
||||
{
|
||||
UIBackgroundTaskIdentifier appSuspendTask;
|
||||
}
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
- (id) init;
|
||||
- (void) dealloc;
|
||||
- (void) applicationDidFinishLaunching: (UIApplication*) application;
|
||||
- (void) applicationWillTerminate: (UIApplication*) application;
|
||||
- (void) applicationDidEnterBackground: (UIApplication*) application;
|
||||
- (void) applicationWillEnterForeground: (UIApplication*) application;
|
||||
- (void) applicationDidBecomeActive: (UIApplication*) application;
|
||||
- (void) applicationWillResignActive: (UIApplication*) application;
|
||||
- (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*) identifier
|
||||
completionHandler: (void (^)(void)) completionHandler;
|
||||
- (void) applicationDidReceiveMemoryWarning: (UIApplication *) application;
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken;
|
||||
- (void) application: (UIApplication*) application
|
||||
didFailToRegisterForRemoteNotificationsWithError: (NSError*) error;
|
||||
- (void) application: (UIApplication*) application
|
||||
didReceiveRemoteNotification: (NSDictionary*) userInfo;
|
||||
- (void) application: (UIApplication*) application
|
||||
didReceiveRemoteNotification: (NSDictionary*) userInfo
|
||||
fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler;
|
||||
- (void) application: (UIApplication*) application
|
||||
handleActionWithIdentifier: (NSString*) identifier
|
||||
forRemoteNotification: (NSDictionary*) userInfo
|
||||
withResponseInfo: (NSDictionary*) responseInfo
|
||||
completionHandler: (void(^)()) completionHandler;
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings;
|
||||
- (void) application: (UIApplication*) application
|
||||
didReceiveLocalNotification: (UILocalNotification*) notification;
|
||||
- (void) application: (UIApplication*) application
|
||||
handleActionWithIdentifier: (NSString*) identifier
|
||||
forLocalNotification: (UILocalNotification*) notification
|
||||
completionHandler: (void(^)()) completionHandler;
|
||||
- (void) application: (UIApplication*) application
|
||||
handleActionWithIdentifier: (NSString*) identifier
|
||||
forLocalNotification: (UILocalNotification*) notification
|
||||
withResponseInfo: (NSDictionary*) responseInfo
|
||||
completionHandler: (void(^)()) completionHandler;
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
- (void) userNotificationCenter: (UNUserNotificationCenter*) center
|
||||
willPresentNotification: (UNNotification*) notification
|
||||
withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler;
|
||||
- (void) userNotificationCenter: (UNUserNotificationCenter*) center
|
||||
didReceiveNotificationResponse: (UNNotificationResponse*) response
|
||||
withCompletionHandler: (void(^)())completionHandler;
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@implementation JuceAppStartupDelegate
|
||||
|
||||
NSObject* _pushNotificationsDelegate;
|
||||
|
||||
- (id) init
|
||||
{
|
||||
self = [super init];
|
||||
appSuspendTask = UIBackgroundTaskInvalid;
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
|
||||
#endif
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) dealloc
|
||||
{
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void) applicationDidFinishLaunching: (UIApplication*) application
|
||||
{
|
||||
ignoreUnused (application);
|
||||
initialiseJuce_GUI();
|
||||
|
||||
if (auto* app = JUCEApplicationBase::createInstance())
|
||||
{
|
||||
if (! app->initialiseApp())
|
||||
exit (app->shutdownApp());
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse; // you must supply an application object for an iOS app!
|
||||
}
|
||||
}
|
||||
|
||||
- (void) applicationWillTerminate: (UIApplication*) application
|
||||
{
|
||||
ignoreUnused (application);
|
||||
JUCEApplicationBase::appWillTerminateByForce();
|
||||
}
|
||||
|
||||
- (void) applicationDidEnterBackground: (UIApplication*) application
|
||||
{
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
{
|
||||
#if JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK
|
||||
appSuspendTask = [application beginBackgroundTaskWithName:@"JUCE Suspend Task" expirationHandler:^{
|
||||
if (appSuspendTask != UIBackgroundTaskInvalid)
|
||||
{
|
||||
[application endBackgroundTask:appSuspendTask];
|
||||
appSuspendTask = UIBackgroundTaskInvalid;
|
||||
}
|
||||
}];
|
||||
|
||||
MessageManager::callAsync ([app] { app->suspended(); });
|
||||
#else
|
||||
ignoreUnused (application);
|
||||
app->suspended();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
- (void) applicationWillEnterForeground: (UIApplication*) application
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
app->resumed();
|
||||
}
|
||||
|
||||
- (void) applicationDidBecomeActive: (UIApplication*) application
|
||||
{
|
||||
application.applicationIconBadgeNumber = 0;
|
||||
|
||||
isIOSAppActive = true;
|
||||
}
|
||||
|
||||
- (void) applicationWillResignActive: (UIApplication*) application
|
||||
{
|
||||
ignoreUnused (application);
|
||||
isIOSAppActive = false;
|
||||
|
||||
for (int i = appBecomingInactiveCallbacks.size(); --i >= 0;)
|
||||
appBecomingInactiveCallbacks.getReference (i)->appBecomingInactive();
|
||||
}
|
||||
|
||||
- (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*)identifier
|
||||
completionHandler: (void (^)(void))completionHandler
|
||||
{
|
||||
ignoreUnused (application);
|
||||
URL::DownloadTask::juce_iosURLSessionNotify (nsStringToJuce (identifier));
|
||||
completionHandler();
|
||||
}
|
||||
|
||||
- (void) applicationDidReceiveMemoryWarning: (UIApplication*) application
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
app->memoryWarningReceived();
|
||||
}
|
||||
|
||||
- (void) setPushNotificationsDelegateToUse: (NSObject*) delegate
|
||||
{
|
||||
_pushNotificationsDelegate = delegate;
|
||||
}
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: &application atIndex:2];
|
||||
[invocation setArgument: &deviceToken atIndex:3];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
didFailToRegisterForRemoteNotificationsWithError: (NSError*) error
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: &application atIndex:2];
|
||||
[invocation setArgument: &error atIndex:3];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
didReceiveRemoteNotification: (NSDictionary*) userInfo
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
SEL selector = @selector (application:didReceiveRemoteNotification:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: &application atIndex:2];
|
||||
[invocation setArgument: &userInfo atIndex:3];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
didReceiveRemoteNotification: (NSDictionary*) userInfo
|
||||
fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
SEL selector = @selector (application:didReceiveRemoteNotification:fetchCompletionHandler:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: &application atIndex:2];
|
||||
[invocation setArgument: &userInfo atIndex:3];
|
||||
[invocation setArgument: &completionHandler atIndex:4];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
handleActionWithIdentifier: (NSString*) identifier
|
||||
forRemoteNotification: (NSDictionary*) userInfo
|
||||
withResponseInfo: (NSDictionary*) responseInfo
|
||||
completionHandler: (void(^)()) completionHandler
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
SEL selector = @selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: &application atIndex:2];
|
||||
[invocation setArgument: &identifier atIndex:3];
|
||||
[invocation setArgument: &userInfo atIndex:4];
|
||||
[invocation setArgument: &responseInfo atIndex:5];
|
||||
[invocation setArgument: &completionHandler atIndex:6];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
SEL selector = @selector (application:didRegisterUserNotificationSettings:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector:selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: &application atIndex: 2];
|
||||
[invocation setArgument: ¬ificationSettings atIndex: 3];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
didReceiveLocalNotification: (UILocalNotification*) notification
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
SEL selector = @selector (application:didReceiveLocalNotification:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: &application atIndex: 2];
|
||||
[invocation setArgument: ¬ification atIndex: 3];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
handleActionWithIdentifier: (NSString*) identifier
|
||||
forLocalNotification: (UILocalNotification*) notification
|
||||
completionHandler: (void(^)()) completionHandler
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
SEL selector = @selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: &application atIndex:2];
|
||||
[invocation setArgument: &identifier atIndex:3];
|
||||
[invocation setArgument: ¬ification atIndex:4];
|
||||
[invocation setArgument: &completionHandler atIndex:5];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) application: (UIApplication*) application
|
||||
handleActionWithIdentifier: (NSString*) identifier
|
||||
forLocalNotification: (UILocalNotification*) notification
|
||||
withResponseInfo: (NSDictionary*) responseInfo
|
||||
completionHandler: (void(^)()) completionHandler
|
||||
{
|
||||
ignoreUnused (application);
|
||||
|
||||
SEL selector = @selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: &application atIndex:2];
|
||||
[invocation setArgument: &identifier atIndex:3];
|
||||
[invocation setArgument: ¬ification atIndex:4];
|
||||
[invocation setArgument: &responseInfo atIndex:5];
|
||||
[invocation setArgument: &completionHandler atIndex:6];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
- (void) userNotificationCenter: (UNUserNotificationCenter*) center
|
||||
willPresentNotification: (UNNotification*) notification
|
||||
withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler
|
||||
{
|
||||
ignoreUnused (center);
|
||||
|
||||
SEL selector = @selector (userNotificationCenter:willPresentNotification:withCompletionHandler:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: ¢er atIndex:2];
|
||||
[invocation setArgument: ¬ification atIndex:3];
|
||||
[invocation setArgument: &completionHandler atIndex:4];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) userNotificationCenter: (UNUserNotificationCenter*) center
|
||||
didReceiveNotificationResponse: (UNNotificationResponse*) response
|
||||
withCompletionHandler: (void(^)()) completionHandler
|
||||
{
|
||||
ignoreUnused (center);
|
||||
|
||||
SEL selector = @selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:);
|
||||
|
||||
if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
|
||||
{
|
||||
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
|
||||
[invocation setSelector: selector];
|
||||
[invocation setTarget: _pushNotificationsDelegate];
|
||||
[invocation setArgument: ¢er atIndex:2];
|
||||
[invocation setArgument: &response atIndex:3];
|
||||
[invocation setArgument: &completionHandler atIndex:4];
|
||||
|
||||
[invocation invoke];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
int juce_iOSMain (int argc, const char* argv[], void* customDelegatePtr);
|
||||
int juce_iOSMain (int argc, const char* argv[], void* customDelegatePtr)
|
||||
{
|
||||
Class delegateClass = (customDelegatePtr != nullptr ? reinterpret_cast<Class> (customDelegatePtr) : [JuceAppStartupDelegate class]);
|
||||
|
||||
return UIApplicationMain (argc, const_cast<char**> (argv), nil, NSStringFromClass (delegateClass));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void LookAndFeel::playAlertSound()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray&, bool, Component*, std::function<void()>)
|
||||
{
|
||||
jassertfalse; // no such thing on iOS!
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DragAndDropContainer::performExternalDragDropOfText (const String&, Component*, std::function<void()>)
|
||||
{
|
||||
jassertfalse; // no such thing on iOS!
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::setScreenSaverEnabled (const bool isEnabled)
|
||||
{
|
||||
if (! SystemStats::isRunningInAppExtensionSandbox())
|
||||
[[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled];
|
||||
}
|
||||
|
||||
bool Desktop::isScreenSaverEnabled()
|
||||
{
|
||||
if (SystemStats::isRunningInAppExtensionSandbox())
|
||||
return true;
|
||||
|
||||
return ! [[UIApplication sharedApplication] isIdleTimerDisabled];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Image detail::WindowingHelpers::createIconForFile (const File&)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SystemClipboard::copyTextToClipboard (const String& text)
|
||||
{
|
||||
[[UIPasteboard generalPasteboard] setValue: juceStringToNS (text)
|
||||
forPasteboardType: @"public.text"];
|
||||
}
|
||||
|
||||
String SystemClipboard::getTextFromClipboard()
|
||||
{
|
||||
return nsStringToJuce ([[UIPasteboard generalPasteboard] string]);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool detail::MouseInputSourceList::addSource()
|
||||
{
|
||||
addSource (sources.size(), MouseInputSource::InputSourceType::touch);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool detail::MouseInputSourceList::canUseTouch() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Desktop::canUseSemiTransparentWindows() noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Desktop::isDarkModeActive() const
|
||||
{
|
||||
if (@available (iOS 12.0, *))
|
||||
return [[[UIScreen mainScreen] traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
static const auto darkModeSelector = @selector (darkModeChanged:);
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
class Desktop::NativeDarkModeChangeDetectorImpl
|
||||
{
|
||||
public:
|
||||
NativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
static DelegateClass delegateClass;
|
||||
delegate.reset ([delegateClass.createInstance() init]);
|
||||
observer.emplace (delegate.get(), darkModeSelector, UIViewComponentPeer::getDarkModeNotificationName(), nil);
|
||||
}
|
||||
|
||||
private:
|
||||
struct DelegateClass final : public ObjCClass<NSObject>
|
||||
{
|
||||
DelegateClass() : ObjCClass<NSObject> ("JUCEDelegate_")
|
||||
{
|
||||
addMethod (darkModeSelector, [] (id, SEL, NSNotification*) { Desktop::getInstance().darkModeChanged(); });
|
||||
registerClass();
|
||||
}
|
||||
};
|
||||
|
||||
NSUniquePtr<NSObject> delegate;
|
||||
Optional<ScopedNotificationCenterObserver> observer;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl)
|
||||
};
|
||||
|
||||
std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
return std::make_unique<NativeDarkModeChangeDetectorImpl>();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Point<float> MouseInputSource::getCurrentRawMousePosition()
|
||||
{
|
||||
return juce_lastMousePos;
|
||||
}
|
||||
|
||||
void MouseInputSource::setRawMousePosition (Point<float>)
|
||||
{
|
||||
}
|
||||
|
||||
double Desktop::getDefaultMasterScale()
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
|
||||
{
|
||||
UIInterfaceOrientation orientation = SystemStats::isRunningInAppExtensionSandbox() ? UIInterfaceOrientationPortrait
|
||||
: getWindowOrientation();
|
||||
|
||||
return Orientations::convertToJuce (orientation);
|
||||
}
|
||||
|
||||
// The most straightforward way of retrieving the screen area available to an iOS app
|
||||
// seems to be to create a new window (which will take up all available space) and to
|
||||
// query its frame.
|
||||
struct TemporaryWindow
|
||||
{
|
||||
UIWindow* window = [[UIWindow alloc] init];
|
||||
~TemporaryWindow() noexcept { [window release]; }
|
||||
};
|
||||
|
||||
static Rectangle<int> getRecommendedWindowBounds()
|
||||
{
|
||||
return convertToRectInt (TemporaryWindow().window.frame);
|
||||
}
|
||||
|
||||
static BorderSize<int> getSafeAreaInsets (float masterScale)
|
||||
{
|
||||
if (@available (iOS 11.0, *))
|
||||
{
|
||||
UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets;
|
||||
return detail::WindowingHelpers::roundToInt (BorderSize<double> { safeInsets.top,
|
||||
safeInsets.left,
|
||||
safeInsets.bottom,
|
||||
safeInsets.right }.multipliedBy (1.0 / (double) masterScale));
|
||||
}
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
auto statusBarSize = [UIApplication sharedApplication].statusBarFrame.size;
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
auto statusBarHeight = jmin (statusBarSize.width, statusBarSize.height);
|
||||
|
||||
return { roundToInt (statusBarHeight / masterScale), 0, 0, 0 };
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Displays::findDisplays (float masterScale)
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
static const auto keyboardShownSelector = @selector (juceKeyboardShown:);
|
||||
static const auto keyboardHiddenSelector = @selector (juceKeyboardHidden:);
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
class OnScreenKeyboardChangeDetectorImpl
|
||||
{
|
||||
public:
|
||||
OnScreenKeyboardChangeDetectorImpl()
|
||||
{
|
||||
static DelegateClass delegateClass;
|
||||
delegate.reset ([delegateClass.createInstance() init]);
|
||||
object_setInstanceVariable (delegate.get(), "owner", this);
|
||||
observers.emplace_back (delegate.get(), keyboardShownSelector, UIKeyboardDidShowNotification, nil);
|
||||
observers.emplace_back (delegate.get(), keyboardHiddenSelector, UIKeyboardDidHideNotification, nil);
|
||||
}
|
||||
|
||||
auto getInsets() const { return insets; }
|
||||
|
||||
private:
|
||||
struct DelegateClass final : public ObjCClass<NSObject>
|
||||
{
|
||||
DelegateClass() : ObjCClass<NSObject> ("JUCEOnScreenKeyboardObserver_")
|
||||
{
|
||||
addIvar<OnScreenKeyboardChangeDetectorImpl*> ("owner");
|
||||
|
||||
addMethod (keyboardShownSelector, [] (id self, SEL, NSNotification* notification)
|
||||
{
|
||||
setKeyboardScreenBounds (self, [&]() -> BorderSize<double>
|
||||
{
|
||||
auto* info = [notification userInfo];
|
||||
|
||||
if (info == nullptr)
|
||||
return {};
|
||||
|
||||
auto* value = static_cast<NSValue*> ([info objectForKey: UIKeyboardFrameEndUserInfoKey]);
|
||||
|
||||
if (value == nullptr)
|
||||
return {};
|
||||
|
||||
auto* display = Desktop::getInstance().getDisplays().getPrimaryDisplay();
|
||||
|
||||
if (display == nullptr)
|
||||
return {};
|
||||
|
||||
const auto rect = convertToRectInt ([value CGRectValue]);
|
||||
|
||||
BorderSize<double> result;
|
||||
|
||||
if (rect.getY() == display->totalArea.getY())
|
||||
result.setTop (rect.getHeight());
|
||||
|
||||
if (rect.getBottom() == display->totalArea.getBottom())
|
||||
result.setBottom (rect.getHeight());
|
||||
|
||||
return result;
|
||||
}());
|
||||
});
|
||||
|
||||
addMethod (keyboardHiddenSelector, [] (id self, SEL, NSNotification*)
|
||||
{
|
||||
setKeyboardScreenBounds (self, {});
|
||||
});
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static void setKeyboardScreenBounds (id self, BorderSize<double> insets)
|
||||
{
|
||||
if (std::exchange (getIvar<OnScreenKeyboardChangeDetectorImpl*> (self, "owner")->insets, insets) != insets)
|
||||
Desktop::getInstance().displays->refresh();
|
||||
}
|
||||
};
|
||||
|
||||
BorderSize<double> insets;
|
||||
NSUniquePtr<NSObject> delegate;
|
||||
std::vector<ScopedNotificationCenterObserver> observers;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnScreenKeyboardChangeDetectorImpl)
|
||||
};
|
||||
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
static OnScreenKeyboardChangeDetectorImpl keyboardChangeDetector;
|
||||
|
||||
UIScreen* s = [UIScreen mainScreen];
|
||||
|
||||
Display d;
|
||||
d.totalArea = convertToRectInt ([s bounds]) / masterScale;
|
||||
d.userArea = getRecommendedWindowBounds() / masterScale;
|
||||
d.safeAreaInsets = getSafeAreaInsets (masterScale);
|
||||
const auto scaledInsets = keyboardChangeDetector.getInsets().multipliedBy (1.0 / (double) masterScale);
|
||||
d.keyboardInsets = detail::WindowingHelpers::roundToInt (scaledInsets);
|
||||
d.isMain = true;
|
||||
d.scale = masterScale * s.scale;
|
||||
d.dpi = 160 * d.scale;
|
||||
|
||||
displays.add (d);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,854 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class LinuxComponentPeer final : public ComponentPeer,
|
||||
private XWindowSystemUtilities::XSettings::Listener
|
||||
{
|
||||
public:
|
||||
LinuxComponentPeer (Component& comp, int windowStyleFlags, ::Window parentToAddTo)
|
||||
: ComponentPeer (comp, windowStyleFlags),
|
||||
isAlwaysOnTop (comp.isAlwaysOnTop())
|
||||
{
|
||||
// it's dangerous to create a window on a thread other than the message thread.
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
const auto* instance = XWindowSystem::getInstance();
|
||||
|
||||
if (! instance->isX11Available())
|
||||
return;
|
||||
|
||||
if (isAlwaysOnTop)
|
||||
++WindowUtilsInternal::numAlwaysOnTopPeers;
|
||||
|
||||
repainter = std::make_unique<LinuxRepaintManager> (*this);
|
||||
|
||||
windowH = instance->createWindow (parentToAddTo, this);
|
||||
parentWindow = parentToAddTo;
|
||||
|
||||
setTitle (component.getName());
|
||||
|
||||
if (auto* xSettings = instance->getXSettings())
|
||||
xSettings->addListener (this);
|
||||
|
||||
getNativeRealtimeModifiers = []() -> ModifierKeys { return XWindowSystem::getInstance()->getNativeRealtimeModifiers(); };
|
||||
|
||||
updateVBlankTimer();
|
||||
}
|
||||
|
||||
~LinuxComponentPeer() override
|
||||
{
|
||||
// it's dangerous to delete a window on a thread other than the message thread.
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
auto* instance = XWindowSystem::getInstance();
|
||||
|
||||
repainter = nullptr;
|
||||
instance->destroyWindow (windowH);
|
||||
|
||||
if (auto* xSettings = instance->getXSettings())
|
||||
xSettings->removeListener (this);
|
||||
|
||||
if (isAlwaysOnTop)
|
||||
--WindowUtilsInternal::numAlwaysOnTopPeers;
|
||||
}
|
||||
|
||||
::Window getWindowHandle() const noexcept
|
||||
{
|
||||
return windowH;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void* getNativeHandle() const override
|
||||
{
|
||||
return reinterpret_cast<void*> (getWindowHandle());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void forceSetBounds (const Rectangle<int>& correctedNewBounds, bool isNowFullScreen)
|
||||
{
|
||||
bounds = correctedNewBounds;
|
||||
|
||||
updateScaleFactorFromNewBounds (bounds, false);
|
||||
|
||||
auto physicalBounds = parentWindow == 0 ? Desktop::getInstance().getDisplays().logicalToPhysical (bounds)
|
||||
: bounds * currentScaleFactor;
|
||||
|
||||
WeakReference<Component> deletionChecker (&component);
|
||||
|
||||
XWindowSystem::getInstance()->setBounds (windowH, physicalBounds, isNowFullScreen);
|
||||
|
||||
fullScreen = isNowFullScreen;
|
||||
|
||||
if (deletionChecker != nullptr)
|
||||
{
|
||||
updateBorderSize();
|
||||
handleMovedOrResized();
|
||||
}
|
||||
}
|
||||
|
||||
void setBounds (const Rectangle<int>& newBounds, bool isNowFullScreen) override
|
||||
{
|
||||
const auto correctedNewBounds = newBounds.withSize (jmax (1, newBounds.getWidth()),
|
||||
jmax (1, newBounds.getHeight()));
|
||||
|
||||
if (bounds != correctedNewBounds || fullScreen != isNowFullScreen)
|
||||
forceSetBounds (correctedNewBounds, isNowFullScreen);
|
||||
}
|
||||
|
||||
Point<int> getScreenPosition (bool physical) const
|
||||
{
|
||||
auto physicalParentPosition = XWindowSystem::getInstance()->getPhysicalParentScreenPosition();
|
||||
auto parentPosition = parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalParentPosition)
|
||||
: physicalParentPosition / currentScaleFactor;
|
||||
|
||||
auto screenBounds = parentWindow == 0 ? bounds
|
||||
: bounds.translated (parentPosition.x, parentPosition.y);
|
||||
|
||||
if (physical)
|
||||
return parentWindow == 0 ? Desktop::getInstance().getDisplays().logicalToPhysical (screenBounds.getTopLeft())
|
||||
: screenBounds.getTopLeft() * currentScaleFactor;
|
||||
|
||||
return screenBounds.getTopLeft();
|
||||
}
|
||||
|
||||
Rectangle<int> getBounds() const override
|
||||
{
|
||||
return bounds;
|
||||
}
|
||||
|
||||
OptionalBorderSize getFrameSizeIfPresent() const override
|
||||
{
|
||||
return windowBorder;
|
||||
}
|
||||
|
||||
BorderSize<int> getFrameSize() const override
|
||||
{
|
||||
const auto optionalBorderSize = getFrameSizeIfPresent();
|
||||
return optionalBorderSize ? (*optionalBorderSize) : BorderSize<int>();
|
||||
}
|
||||
|
||||
Point<float> localToGlobal (Point<float> relativePosition) override
|
||||
{
|
||||
return localToGlobal (*this, relativePosition);
|
||||
}
|
||||
|
||||
Point<float> globalToLocal (Point<float> screenPosition) override
|
||||
{
|
||||
return globalToLocal (*this, screenPosition);
|
||||
}
|
||||
|
||||
using ComponentPeer::localToGlobal;
|
||||
using ComponentPeer::globalToLocal;
|
||||
|
||||
//==============================================================================
|
||||
StringArray getAvailableRenderingEngines() override
|
||||
{
|
||||
return { "Software Renderer" };
|
||||
}
|
||||
|
||||
void setVisible (bool shouldBeVisible) override
|
||||
{
|
||||
XWindowSystem::getInstance()->setVisible (windowH, shouldBeVisible);
|
||||
}
|
||||
|
||||
void setTitle (const String& title) override
|
||||
{
|
||||
XWindowSystem::getInstance()->setTitle (windowH, title);
|
||||
}
|
||||
|
||||
void setMinimised (bool shouldBeMinimised) override
|
||||
{
|
||||
if (shouldBeMinimised)
|
||||
XWindowSystem::getInstance()->setMinimised (windowH, shouldBeMinimised);
|
||||
else
|
||||
setVisible (true);
|
||||
}
|
||||
|
||||
bool isMinimised() const override
|
||||
{
|
||||
return XWindowSystem::getInstance()->isMinimised (windowH);
|
||||
}
|
||||
|
||||
void setFullScreen (bool shouldBeFullScreen) override
|
||||
{
|
||||
auto r = lastNonFullscreenBounds; // (get a copy of this before de-minimising)
|
||||
|
||||
setMinimised (false);
|
||||
|
||||
if (fullScreen != shouldBeFullScreen)
|
||||
{
|
||||
const auto usingNativeTitleBar = ((styleFlags & windowHasTitleBar) != 0);
|
||||
|
||||
if (usingNativeTitleBar)
|
||||
XWindowSystem::getInstance()->setMaximised (windowH, shouldBeFullScreen);
|
||||
|
||||
if (shouldBeFullScreen)
|
||||
r = usingNativeTitleBar ? XWindowSystem::getInstance()->getWindowBounds (windowH, parentWindow)
|
||||
: Desktop::getInstance().getDisplays().getDisplayForRect (bounds)->userArea;
|
||||
|
||||
if (! r.isEmpty())
|
||||
setBounds (detail::ScalingHelpers::scaledScreenPosToUnscaled (component, r), shouldBeFullScreen);
|
||||
|
||||
component.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
bool isFullScreen() const override
|
||||
{
|
||||
return fullScreen;
|
||||
}
|
||||
|
||||
bool contains (Point<int> localPos, bool trueIfInAChildWindow) const override
|
||||
{
|
||||
if (! bounds.withZeroOrigin().contains (localPos))
|
||||
return false;
|
||||
|
||||
for (int i = Desktop::getInstance().getNumComponents(); --i >= 0;)
|
||||
{
|
||||
auto* c = Desktop::getInstance().getComponent (i);
|
||||
|
||||
if (c == &component)
|
||||
break;
|
||||
|
||||
if (! c->isVisible())
|
||||
continue;
|
||||
|
||||
auto* otherPeer = c->getPeer();
|
||||
jassert (otherPeer == nullptr || dynamic_cast<LinuxComponentPeer*> (c->getPeer()) != nullptr);
|
||||
|
||||
if (auto* peer = static_cast<LinuxComponentPeer*> (otherPeer))
|
||||
if (peer->contains (globalToLocal (*peer, localToGlobal (*this, localPos.toFloat())).roundToInt(), true))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trueIfInAChildWindow)
|
||||
return true;
|
||||
|
||||
return XWindowSystem::getInstance()->contains (windowH, localPos * currentScaleFactor);
|
||||
}
|
||||
|
||||
void toFront (bool makeActive) override
|
||||
{
|
||||
if (makeActive)
|
||||
{
|
||||
setVisible (true);
|
||||
grabFocus();
|
||||
}
|
||||
|
||||
XWindowSystem::getInstance()->toFront (windowH, makeActive);
|
||||
handleBroughtToFront();
|
||||
}
|
||||
|
||||
void toBehind (ComponentPeer* other) override
|
||||
{
|
||||
if (auto* otherPeer = dynamic_cast<LinuxComponentPeer*> (other))
|
||||
{
|
||||
if (otherPeer->styleFlags & windowIsTemporary)
|
||||
return;
|
||||
|
||||
setMinimised (false);
|
||||
XWindowSystem::getInstance()->toBehind (windowH, otherPeer->windowH);
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse; // wrong type of window?
|
||||
}
|
||||
}
|
||||
|
||||
bool isFocused() const override
|
||||
{
|
||||
return XWindowSystem::getInstance()->isFocused (windowH);
|
||||
}
|
||||
|
||||
void grabFocus() override
|
||||
{
|
||||
if (XWindowSystem::getInstance()->grabFocus (windowH))
|
||||
isActiveApplication = true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void repaint (const Rectangle<int>& area) override
|
||||
{
|
||||
if (repainter != nullptr)
|
||||
repainter->repaint (area.getIntersection (bounds.withZeroOrigin()));
|
||||
}
|
||||
|
||||
void performAnyPendingRepaintsNow() override
|
||||
{
|
||||
if (repainter != nullptr)
|
||||
repainter->performAnyPendingRepaintsNow();
|
||||
}
|
||||
|
||||
void setIcon (const Image& newIcon) override
|
||||
{
|
||||
XWindowSystem::getInstance()->setIcon (windowH, newIcon);
|
||||
}
|
||||
|
||||
double getPlatformScaleFactor() const noexcept override
|
||||
{
|
||||
return currentScaleFactor;
|
||||
}
|
||||
|
||||
void setAlpha (float) override {}
|
||||
bool setAlwaysOnTop (bool) override { return false; }
|
||||
void textInputRequired (Point<int>, TextInputTarget&) override {}
|
||||
|
||||
//==============================================================================
|
||||
void addOpenGLRepaintListener (Component* dummy)
|
||||
{
|
||||
if (dummy != nullptr)
|
||||
glRepaintListeners.addIfNotAlreadyThere (dummy);
|
||||
}
|
||||
|
||||
void removeOpenGLRepaintListener (Component* dummy)
|
||||
{
|
||||
if (dummy != nullptr)
|
||||
glRepaintListeners.removeAllInstancesOf (dummy);
|
||||
}
|
||||
|
||||
void repaintOpenGLContexts()
|
||||
{
|
||||
for (auto* c : glRepaintListeners)
|
||||
c->handleCommandMessage (0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
::Window getParentWindow() { return parentWindow; }
|
||||
void setParentWindow (::Window newParent) { parentWindow = newParent; }
|
||||
|
||||
//==============================================================================
|
||||
bool isConstrainedNativeWindow() const
|
||||
{
|
||||
return constrainer != nullptr
|
||||
&& (styleFlags & (windowHasTitleBar | windowIsResizable)) == (windowHasTitleBar | windowIsResizable)
|
||||
&& ! isKioskMode();
|
||||
}
|
||||
|
||||
void updateWindowBounds()
|
||||
{
|
||||
if (windowH == 0)
|
||||
{
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isConstrainedNativeWindow())
|
||||
XWindowSystem::getInstance()->updateConstraints (windowH);
|
||||
|
||||
auto physicalBounds = XWindowSystem::getInstance()->getWindowBounds (windowH, parentWindow);
|
||||
|
||||
updateScaleFactorFromNewBounds (physicalBounds, true);
|
||||
|
||||
bounds = parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalBounds)
|
||||
: physicalBounds / currentScaleFactor;
|
||||
|
||||
updateVBlankTimer();
|
||||
}
|
||||
|
||||
void updateBorderSize()
|
||||
{
|
||||
if ((styleFlags & windowHasTitleBar) == 0)
|
||||
{
|
||||
windowBorder = ComponentPeer::OptionalBorderSize { BorderSize<int>() };
|
||||
}
|
||||
else if (! windowBorder
|
||||
|| ((*windowBorder).getTopAndBottom() == 0 && (*windowBorder).getLeftAndRight() == 0))
|
||||
{
|
||||
windowBorder = [&]()
|
||||
{
|
||||
if (auto unscaledBorderSize = XWindowSystem::getInstance()->getBorderSize (windowH))
|
||||
return OptionalBorderSize { (*unscaledBorderSize).multipliedBy (1.0 / currentScaleFactor) };
|
||||
|
||||
return OptionalBorderSize {};
|
||||
}();
|
||||
}
|
||||
}
|
||||
|
||||
bool setWindowAssociation (::Window windowIn)
|
||||
{
|
||||
clearWindowAssociation();
|
||||
association = { this, windowIn };
|
||||
return association.isValid();
|
||||
}
|
||||
|
||||
void clearWindowAssociation() { association = {}; }
|
||||
|
||||
void startHostManagedResize (Point<int>, ResizableBorderComponent::Zone zone) override
|
||||
{
|
||||
XWindowSystem::getInstance()->startHostManagedResize (windowH, zone);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static bool isActiveApplication;
|
||||
bool focused = false;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class LinuxRepaintManager
|
||||
{
|
||||
public:
|
||||
LinuxRepaintManager (LinuxComponentPeer& p)
|
||||
: peer (p),
|
||||
isSemiTransparentWindow ((peer.getStyleFlags() & ComponentPeer::windowIsSemiTransparent) != 0)
|
||||
{
|
||||
}
|
||||
|
||||
void dispatchDeferredRepaints()
|
||||
{
|
||||
XWindowSystem::getInstance()->processPendingPaintsForWindow (peer.windowH);
|
||||
|
||||
if (XWindowSystem::getInstance()->getNumPaintsPendingForWindow (peer.windowH) > 0)
|
||||
return;
|
||||
|
||||
if (! regionsNeedingRepaint.isEmpty())
|
||||
performAnyPendingRepaintsNow();
|
||||
else if (Time::getApproximateMillisecondCounter() > lastTimeImageUsed + 3000)
|
||||
image = Image();
|
||||
}
|
||||
|
||||
void repaint (Rectangle<int> area)
|
||||
{
|
||||
regionsNeedingRepaint.add (area * peer.currentScaleFactor);
|
||||
}
|
||||
|
||||
void performAnyPendingRepaintsNow()
|
||||
{
|
||||
if (XWindowSystem::getInstance()->getNumPaintsPendingForWindow (peer.windowH) > 0)
|
||||
return;
|
||||
|
||||
auto originalRepaintRegion = regionsNeedingRepaint;
|
||||
regionsNeedingRepaint.clear();
|
||||
auto totalArea = originalRepaintRegion.getBounds();
|
||||
|
||||
if (! totalArea.isEmpty())
|
||||
{
|
||||
const auto wasImageNull = image.isNull();
|
||||
|
||||
if (wasImageNull || image.getWidth() < totalArea.getWidth()
|
||||
|| image.getHeight() < totalArea.getHeight())
|
||||
{
|
||||
image = XWindowSystem::getInstance()->createImage (isSemiTransparentWindow,
|
||||
totalArea.getWidth(), totalArea.getHeight(),
|
||||
useARGBImagesForRendering);
|
||||
if (wasImageNull)
|
||||
{
|
||||
// After calling createImage() XWindowSystem::getWindowBounds() will return
|
||||
// changed coordinates that look like the result of some position
|
||||
// defaulting mechanism. If we handle a configureNotifyEvent after
|
||||
// createImage() and before we would issue new, valid coordinates, we will
|
||||
// apply these default, unwanted coordinates to our window. To avoid that
|
||||
// we immediately send another positioning message to guarantee that the
|
||||
// next configureNotifyEvent will read valid values.
|
||||
//
|
||||
// This issue only occurs right after peer creation, when the image is
|
||||
// null. Updating when only the width or height is changed would lead to
|
||||
// incorrect behaviour.
|
||||
peer.forceSetBounds (detail::ScalingHelpers::scaledScreenPosToUnscaled (peer.component, peer.component.getBoundsInParent()),
|
||||
peer.isFullScreen());
|
||||
}
|
||||
}
|
||||
|
||||
RectangleList<int> adjustedList (originalRepaintRegion);
|
||||
adjustedList.offsetAll (-totalArea.getX(), -totalArea.getY());
|
||||
|
||||
if (XWindowSystem::getInstance()->canUseARGBImages())
|
||||
for (auto& i : originalRepaintRegion)
|
||||
image.clear (i - totalArea.getPosition());
|
||||
|
||||
{
|
||||
auto context = peer.getComponent().getLookAndFeel()
|
||||
.createGraphicsContext (image, -totalArea.getPosition(), adjustedList);
|
||||
|
||||
context->addTransform (AffineTransform::scale ((float) peer.currentScaleFactor));
|
||||
peer.handlePaint (*context);
|
||||
}
|
||||
|
||||
for (auto& i : originalRepaintRegion)
|
||||
XWindowSystem::getInstance()->blitToWindow (peer.windowH, image, i, totalArea);
|
||||
}
|
||||
|
||||
lastTimeImageUsed = Time::getApproximateMillisecondCounter();
|
||||
}
|
||||
|
||||
private:
|
||||
LinuxComponentPeer& peer;
|
||||
const bool isSemiTransparentWindow;
|
||||
Image image;
|
||||
uint32 lastTimeImageUsed = 0;
|
||||
RectangleList<int> regionsNeedingRepaint;
|
||||
|
||||
bool useARGBImagesForRendering = XWindowSystem::getInstance()->canUseARGBImages();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (LinuxRepaintManager)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
template <typename This>
|
||||
static Point<float> localToGlobal (This& t, Point<float> relativePosition)
|
||||
{
|
||||
return relativePosition + t.getScreenPosition (false).toFloat();
|
||||
}
|
||||
|
||||
template <typename This>
|
||||
static Point<float> globalToLocal (This& t, Point<float> screenPosition)
|
||||
{
|
||||
return screenPosition - t.getScreenPosition (false).toFloat();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void settingChanged (const XWindowSystemUtilities::XSetting& settingThatHasChanged) override
|
||||
{
|
||||
static StringArray possibleSettings { XWindowSystem::getWindowScalingFactorSettingName(),
|
||||
"Gdk/UnscaledDPI",
|
||||
"Xft/DPI" };
|
||||
|
||||
if (possibleSettings.contains (settingThatHasChanged.name))
|
||||
forceDisplayUpdate();
|
||||
}
|
||||
|
||||
void updateScaleFactorFromNewBounds (const Rectangle<int>& newBounds, bool isPhysical)
|
||||
{
|
||||
Point<int> translation = (parentWindow != 0 ? getScreenPosition (isPhysical) : Point<int>());
|
||||
const auto& desktop = Desktop::getInstance();
|
||||
|
||||
if (auto* display = desktop.getDisplays().getDisplayForRect (newBounds.translated (translation.x, translation.y),
|
||||
isPhysical))
|
||||
{
|
||||
auto newScaleFactor = display->scale / desktop.getGlobalScaleFactor();
|
||||
|
||||
if (! approximatelyEqual (newScaleFactor, currentScaleFactor))
|
||||
{
|
||||
currentScaleFactor = newScaleFactor;
|
||||
scaleFactorListeners.call ([&] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (currentScaleFactor); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onVBlank()
|
||||
{
|
||||
vBlankListeners.call ([] (auto& l) { l.onVBlank(); });
|
||||
|
||||
if (repainter != nullptr)
|
||||
repainter->dispatchDeferredRepaints();
|
||||
}
|
||||
|
||||
void updateVBlankTimer()
|
||||
{
|
||||
if (auto* display = Desktop::getInstance().getDisplays().getDisplayForRect (bounds))
|
||||
{
|
||||
// Some systems fail to set an explicit refresh rate, or ask for a refresh rate of 0
|
||||
// (observed on Raspbian Bullseye over VNC). In these situations, use a fallback value.
|
||||
const auto newIntFrequencyHz = roundToInt (display->verticalFrequencyHz.value_or (0.0));
|
||||
const auto frequencyToUse = newIntFrequencyHz != 0 ? newIntFrequencyHz : 100;
|
||||
|
||||
if (vBlankManager.getTimerInterval() != frequencyToUse)
|
||||
vBlankManager.startTimerHz (frequencyToUse);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<LinuxRepaintManager> repainter;
|
||||
TimedCallback vBlankManager { [this]() { onVBlank(); } };
|
||||
|
||||
::Window windowH = {}, parentWindow = {};
|
||||
Rectangle<int> bounds;
|
||||
ComponentPeer::OptionalBorderSize windowBorder;
|
||||
bool fullScreen = false, isAlwaysOnTop = false;
|
||||
double currentScaleFactor = 1.0;
|
||||
Array<Component*> glRepaintListeners;
|
||||
ScopedWindowAssociation association;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LinuxComponentPeer)
|
||||
};
|
||||
|
||||
bool LinuxComponentPeer::isActiveApplication = false;
|
||||
|
||||
//==============================================================================
|
||||
ComponentPeer* Component::createNewPeer (int styleFlags, void* nativeWindowToAttachTo)
|
||||
{
|
||||
return new LinuxComponentPeer (*this, styleFlags, (::Window) nativeWindowToAttachTo);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_API bool JUCE_CALLTYPE Process::isForegroundProcess() { return LinuxComponentPeer::isActiveApplication; }
|
||||
|
||||
JUCE_API void JUCE_CALLTYPE Process::makeForegroundProcess() {}
|
||||
JUCE_API void JUCE_CALLTYPE Process::hide() {}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::setKioskComponent (Component* comp, bool enableOrDisable, bool)
|
||||
{
|
||||
if (enableOrDisable)
|
||||
comp->setBounds (getDisplays().getDisplayForRect (comp->getScreenBounds())->totalArea);
|
||||
}
|
||||
|
||||
void Displays::findDisplays (float masterScale)
|
||||
{
|
||||
if (XWindowSystem::getInstance()->getDisplay() != nullptr)
|
||||
{
|
||||
displays = XWindowSystem::getInstance()->findDisplays (masterScale);
|
||||
|
||||
if (! displays.isEmpty())
|
||||
updateToLogical();
|
||||
}
|
||||
}
|
||||
|
||||
bool Desktop::canUseSemiTransparentWindows() noexcept
|
||||
{
|
||||
return XWindowSystem::getInstance()->canUseSemiTransparentWindows();
|
||||
}
|
||||
|
||||
class Desktop::NativeDarkModeChangeDetectorImpl : private XWindowSystemUtilities::XSettings::Listener
|
||||
{
|
||||
public:
|
||||
NativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
const auto* windowSystem = XWindowSystem::getInstance();
|
||||
|
||||
if (auto* xSettings = windowSystem->getXSettings())
|
||||
xSettings->addListener (this);
|
||||
|
||||
darkModeEnabled = windowSystem->isDarkModeActive();
|
||||
}
|
||||
|
||||
~NativeDarkModeChangeDetectorImpl() override
|
||||
{
|
||||
if (auto* windowSystem = XWindowSystem::getInstanceWithoutCreating())
|
||||
if (auto* xSettings = windowSystem->getXSettings())
|
||||
xSettings->removeListener (this);
|
||||
}
|
||||
|
||||
bool isDarkModeEnabled() const noexcept { return darkModeEnabled; }
|
||||
|
||||
private:
|
||||
void settingChanged (const XWindowSystemUtilities::XSetting& settingThatHasChanged) override
|
||||
{
|
||||
if (settingThatHasChanged.name == XWindowSystem::getThemeNameSettingName())
|
||||
{
|
||||
const auto wasDarkModeEnabled = std::exchange (darkModeEnabled, XWindowSystem::getInstance()->isDarkModeActive());
|
||||
|
||||
if (darkModeEnabled != wasDarkModeEnabled)
|
||||
Desktop::getInstance().darkModeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool darkModeEnabled = false;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl)
|
||||
};
|
||||
|
||||
std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
return std::make_unique<NativeDarkModeChangeDetectorImpl>();
|
||||
}
|
||||
|
||||
bool Desktop::isDarkModeActive() const
|
||||
{
|
||||
return nativeDarkModeChangeDetectorImpl->isDarkModeEnabled();
|
||||
}
|
||||
|
||||
static bool screenSaverAllowed = true;
|
||||
|
||||
void Desktop::setScreenSaverEnabled (bool isEnabled)
|
||||
{
|
||||
if (screenSaverAllowed != isEnabled)
|
||||
{
|
||||
screenSaverAllowed = isEnabled;
|
||||
XWindowSystem::getInstance()->setScreenSaverEnabled (screenSaverAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
bool Desktop::isScreenSaverEnabled()
|
||||
{
|
||||
return screenSaverAllowed;
|
||||
}
|
||||
|
||||
double Desktop::getDefaultMasterScale() { return 1.0; }
|
||||
|
||||
Desktop::DisplayOrientation Desktop::getCurrentOrientation() const { return upright; }
|
||||
void Desktop::allowedOrientationsChanged() {}
|
||||
|
||||
//==============================================================================
|
||||
bool detail::MouseInputSourceList::addSource()
|
||||
{
|
||||
if (sources.isEmpty())
|
||||
{
|
||||
addSource (0, MouseInputSource::InputSourceType::mouse);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool detail::MouseInputSourceList::canUseTouch() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Point<float> MouseInputSource::getCurrentRawMousePosition()
|
||||
{
|
||||
return Desktop::getInstance().getDisplays().physicalToLogical (XWindowSystem::getInstance()->getCurrentMousePosition());
|
||||
}
|
||||
|
||||
void MouseInputSource::setRawMousePosition (Point<float> newPosition)
|
||||
{
|
||||
XWindowSystem::getInstance()->setMousePosition (Desktop::getInstance().getDisplays().logicalToPhysical (newPosition));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class MouseCursor::PlatformSpecificHandle
|
||||
{
|
||||
public:
|
||||
explicit PlatformSpecificHandle (const MouseCursor::StandardCursorType type)
|
||||
: cursorHandle (makeHandle (type)) {}
|
||||
|
||||
explicit PlatformSpecificHandle (const detail::CustomMouseCursorInfo& info)
|
||||
: cursorHandle (makeHandle (info)) {}
|
||||
|
||||
~PlatformSpecificHandle()
|
||||
{
|
||||
if (cursorHandle != Cursor{})
|
||||
XWindowSystem::getInstance()->deleteMouseCursor (cursorHandle);
|
||||
}
|
||||
|
||||
static void showInWindow (PlatformSpecificHandle* handle, ComponentPeer* peer)
|
||||
{
|
||||
const auto cursor = handle != nullptr ? handle->cursorHandle : Cursor{};
|
||||
|
||||
if (peer != nullptr)
|
||||
XWindowSystem::getInstance()->showCursor ((::Window) peer->getNativeHandle(), cursor);
|
||||
}
|
||||
|
||||
private:
|
||||
static Cursor makeHandle (const detail::CustomMouseCursorInfo& info)
|
||||
{
|
||||
const auto image = info.image.getImage();
|
||||
return XWindowSystem::getInstance()->createCustomMouseCursorInfo (image.rescaled ((int) (image.getWidth() / info.image.getScale()),
|
||||
(int) (image.getHeight() / info.image.getScale())), info.hotspot);
|
||||
}
|
||||
|
||||
static Cursor makeHandle (MouseCursor::StandardCursorType type)
|
||||
{
|
||||
return XWindowSystem::getInstance()->createStandardMouseCursor (type);
|
||||
}
|
||||
|
||||
Cursor cursorHandle;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE (PlatformSpecificHandle)
|
||||
JUCE_DECLARE_NON_MOVEABLE (PlatformSpecificHandle)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static LinuxComponentPeer* getPeerForDragEvent (Component* sourceComp)
|
||||
{
|
||||
if (sourceComp == nullptr)
|
||||
if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource (0))
|
||||
sourceComp = draggingSource->getComponentUnderMouse();
|
||||
|
||||
if (sourceComp != nullptr)
|
||||
if (auto* lp = dynamic_cast<LinuxComponentPeer*> (sourceComp->getPeer()))
|
||||
return lp;
|
||||
|
||||
jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event!
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool canMoveFiles,
|
||||
Component* sourceComp, std::function<void()> callback)
|
||||
{
|
||||
if (files.isEmpty())
|
||||
return false;
|
||||
|
||||
if (auto* peer = getPeerForDragEvent (sourceComp))
|
||||
return XWindowSystem::getInstance()->externalDragFileInit (peer, files, canMoveFiles, std::move (callback));
|
||||
|
||||
// This method must be called in response to a component's mouseDown or mouseDrag event!
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComp,
|
||||
std::function<void()> callback)
|
||||
{
|
||||
if (text.isEmpty())
|
||||
return false;
|
||||
|
||||
if (auto* peer = getPeerForDragEvent (sourceComp))
|
||||
return XWindowSystem::getInstance()->externalDragTextInit (peer, text, std::move (callback));
|
||||
|
||||
// This method must be called in response to a component's mouseDown or mouseDrag event!
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SystemClipboard::copyTextToClipboard (const String& clipText)
|
||||
{
|
||||
XWindowSystem::getInstance()->copyTextToClipboard (clipText);
|
||||
}
|
||||
|
||||
String SystemClipboard::getTextFromClipboard()
|
||||
{
|
||||
return XWindowSystem::getInstance()->getTextFromClipboard();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool KeyPress::isKeyCurrentlyDown (int keyCode)
|
||||
{
|
||||
return XWindowSystem::getInstance()->isKeyCurrentlyDown (keyCode);
|
||||
}
|
||||
|
||||
void LookAndFeel::playAlertSound()
|
||||
{
|
||||
std::cout << "\a" << std::flush;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Image detail::WindowingHelpers::createIconForFile (const File&)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void juce_LinuxAddRepaintListener (ComponentPeer* peer, Component* dummy);
|
||||
void juce_LinuxAddRepaintListener (ComponentPeer* peer, Component* dummy)
|
||||
{
|
||||
if (auto* linuxPeer = dynamic_cast<LinuxComponentPeer*> (peer))
|
||||
linuxPeer->addOpenGLRepaintListener (dummy);
|
||||
}
|
||||
|
||||
void juce_LinuxRemoveRepaintListener (ComponentPeer* peer, Component* dummy);
|
||||
void juce_LinuxRemoveRepaintListener (ComponentPeer* peer, Component* dummy)
|
||||
{
|
||||
if (auto* linuxPeer = dynamic_cast<LinuxComponentPeer*> (peer))
|
||||
linuxPeer->removeOpenGLRepaintListener (dummy);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,588 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
void LookAndFeel::playAlertSound()
|
||||
{
|
||||
NSBeep();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static NSRect getDragRect (NSView* view, NSEvent* event)
|
||||
{
|
||||
auto eventPos = [event locationInWindow];
|
||||
|
||||
return [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
|
||||
fromView: nil];
|
||||
}
|
||||
|
||||
static NSView* getNSViewForDragEvent (Component* sourceComp)
|
||||
{
|
||||
if (sourceComp == nullptr)
|
||||
if (auto* draggingSource = Desktop::getInstance().getDraggingMouseSource (0))
|
||||
sourceComp = draggingSource->getComponentUnderMouse();
|
||||
|
||||
if (sourceComp != nullptr)
|
||||
return (NSView*) sourceComp->getWindowHandle();
|
||||
|
||||
jassertfalse; // This method must be called in response to a component's mouseDown or mouseDrag event!
|
||||
return nil;
|
||||
}
|
||||
|
||||
class NSDraggingSourceHelper final : public ObjCClass<NSObject<NSDraggingSource>>
|
||||
{
|
||||
public:
|
||||
static void setText (id self, const String& text)
|
||||
{
|
||||
object_setInstanceVariable (self, "text", new String (text));
|
||||
}
|
||||
|
||||
static void setCompletionCallback (id self, std::function<void()> cb)
|
||||
{
|
||||
object_setInstanceVariable (self, "callback", new std::function<void()> (cb));
|
||||
}
|
||||
|
||||
static void setDragOperation (id self, NSDragOperation op)
|
||||
{
|
||||
object_setInstanceVariable (self, "operation", new NSDragOperation (op));
|
||||
}
|
||||
|
||||
static NSDraggingSourceHelper& get()
|
||||
{
|
||||
static NSDraggingSourceHelper draggingSourceHelper;
|
||||
return draggingSourceHelper;
|
||||
}
|
||||
|
||||
private:
|
||||
NSDraggingSourceHelper()
|
||||
: ObjCClass ("JUCENSDraggingSourceHelper_")
|
||||
{
|
||||
addIvar<std::function<void()>*> ("callback");
|
||||
addIvar<String*> ("text");
|
||||
addIvar<NSDragOperation*> ("operation");
|
||||
|
||||
addMethod (@selector (dealloc), [] (id self, SEL)
|
||||
{
|
||||
delete getIvar<String*> (self, "text");
|
||||
delete getIvar<std::function<void()>*> (self, "callback");
|
||||
delete getIvar<NSDragOperation*> (self, "operation");
|
||||
|
||||
sendSuperclassMessage<void> (self, @selector (dealloc));
|
||||
});
|
||||
|
||||
addMethod (@selector (pasteboard:item:provideDataForType:), [] (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type)
|
||||
{
|
||||
if ([type compare: NSPasteboardTypeString] == NSOrderedSame)
|
||||
if (auto* text = getIvar<String*> (self, "text"))
|
||||
[sender setData: [juceStringToNS (*text) dataUsingEncoding: NSUTF8StringEncoding]
|
||||
forType: NSPasteboardTypeString];
|
||||
});
|
||||
|
||||
addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), [] (id self, SEL, NSDraggingSession*, NSDraggingContext)
|
||||
{
|
||||
return *getIvar<NSDragOperation*> (self, "operation");
|
||||
});
|
||||
|
||||
addMethod (@selector (draggingSession:endedAtPoint:operation:), [] (id self, SEL, NSDraggingSession*, NSPoint p, NSDragOperation)
|
||||
{
|
||||
// Our view doesn't receive a mouse up when the drag ends so we need to generate one here and send it...
|
||||
if (auto* view = getNSViewForDragEvent (nullptr))
|
||||
if (auto* cgEvent = CGEventCreateMouseEvent (nullptr, kCGEventLeftMouseUp, CGPointMake (p.x, p.y), kCGMouseButtonLeft))
|
||||
if (id e = [NSEvent eventWithCGEvent: cgEvent])
|
||||
[view mouseUp: e];
|
||||
|
||||
if (auto* cb = getIvar<std::function<void()>*> (self, "callback"))
|
||||
cb->operator()();
|
||||
});
|
||||
|
||||
addProtocol (@protocol (NSPasteboardItemDataProvider));
|
||||
|
||||
registerClass();
|
||||
}
|
||||
};
|
||||
|
||||
bool DragAndDropContainer::performExternalDragDropOfText (const String& text, Component* sourceComponent,
|
||||
std::function<void()> callback)
|
||||
{
|
||||
if (text.isEmpty())
|
||||
return false;
|
||||
|
||||
if (auto* view = getNSViewForDragEvent (sourceComponent))
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
if (auto event = [[view window] currentEvent])
|
||||
{
|
||||
id helper = [NSDraggingSourceHelper::get().createInstance() init];
|
||||
NSDraggingSourceHelper::setText (helper, text);
|
||||
NSDraggingSourceHelper::setDragOperation (helper, NSDragOperationCopy);
|
||||
|
||||
if (callback != nullptr)
|
||||
NSDraggingSourceHelper::setCompletionCallback (helper, callback);
|
||||
|
||||
auto pasteboardItem = [[NSPasteboardItem new] autorelease];
|
||||
[pasteboardItem setDataProvider: helper
|
||||
forTypes: [NSArray arrayWithObjects: NSPasteboardTypeString, nil]];
|
||||
|
||||
auto dragItem = [[[NSDraggingItem alloc] initWithPasteboardWriter: pasteboardItem] autorelease];
|
||||
|
||||
NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: nsEmptyString()];
|
||||
[dragItem setDraggingFrame: getDragRect (view, event) contents: image];
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion")
|
||||
if (auto session = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem]
|
||||
event: event
|
||||
source: helper])
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
{
|
||||
session.animatesToStartingPositionsOnCancelOrFail = YES;
|
||||
session.draggingFormation = NSDraggingFormationNone;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, bool canMoveFiles,
|
||||
Component* sourceComponent, std::function<void()> callback)
|
||||
{
|
||||
if (files.isEmpty())
|
||||
return false;
|
||||
|
||||
if (auto* view = getNSViewForDragEvent (sourceComponent))
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
if (auto event = [[view window] currentEvent])
|
||||
{
|
||||
auto dragItems = [[[NSMutableArray alloc] init] autorelease];
|
||||
|
||||
for (auto& filename : files)
|
||||
{
|
||||
auto* nsFilename = juceStringToNS (filename);
|
||||
auto fileURL = [NSURL fileURLWithPath: nsFilename];
|
||||
auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: fileURL];
|
||||
|
||||
auto eventPos = [event locationInWindow];
|
||||
auto dragRect = [view convertRect: NSMakeRect (eventPos.x - 16.0f, eventPos.y - 16.0f, 32.0f, 32.0f)
|
||||
fromView: nil];
|
||||
auto dragImage = [[NSWorkspace sharedWorkspace] iconForFile: nsFilename];
|
||||
[dragItem setDraggingFrame: dragRect
|
||||
contents: dragImage];
|
||||
|
||||
[dragItems addObject: dragItem];
|
||||
[dragItem release];
|
||||
}
|
||||
|
||||
auto helper = [NSDraggingSourceHelper::get().createInstance() autorelease];
|
||||
|
||||
if (callback != nullptr)
|
||||
NSDraggingSourceHelper::setCompletionCallback (helper, callback);
|
||||
|
||||
NSDraggingSourceHelper::setDragOperation (helper, canMoveFiles ? NSDragOperationMove
|
||||
: NSDragOperationCopy);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnullable-to-nonnull-conversion")
|
||||
return [view beginDraggingSessionWithItems: dragItems
|
||||
event: event
|
||||
source: helper] != nullptr;
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool Desktop::canUseSemiTransparentWindows() noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Point<float> MouseInputSource::getCurrentRawMousePosition()
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
auto p = [NSEvent mouseLocation];
|
||||
return { (float) p.x, (float) (getMainScreenHeight() - p.y) };
|
||||
}
|
||||
}
|
||||
|
||||
static ComponentPeer* findPeerContainingPoint (Point<float> globalPos)
|
||||
{
|
||||
for (int i = 0; i < juce::ComponentPeer::getNumPeers(); ++i)
|
||||
{
|
||||
auto* peer = juce::ComponentPeer::getPeer (i);
|
||||
|
||||
if (peer->contains (peer->globalToLocal (globalPos).toInt(), false))
|
||||
return peer;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MouseInputSource::setRawMousePosition (Point<float> newPosition)
|
||||
{
|
||||
const auto oldPosition = Desktop::getInstance().getMainMouseSource().getRawScreenPosition();
|
||||
|
||||
// this rubbish needs to be done around the warp call, to avoid causing a
|
||||
// bizarre glitch..
|
||||
CGAssociateMouseAndMouseCursorPosition (false);
|
||||
CGWarpMouseCursorPosition (convertToCGPoint (newPosition));
|
||||
CGAssociateMouseAndMouseCursorPosition (true);
|
||||
|
||||
// Mouse enter and exit events seem to be always generated as a consequence of programmatically
|
||||
// moving the mouse. However, when the mouse stays within the same peer no mouse move event is
|
||||
// generated, and we lose track of the correct Component under the mouse. Hence, we need to
|
||||
// generate this missing event here.
|
||||
if (auto* peer = findPeerContainingPoint (newPosition); peer != nullptr
|
||||
&& peer == findPeerContainingPoint (oldPosition))
|
||||
{
|
||||
peer->handleMouseEvent (MouseInputSource::InputSourceType::mouse,
|
||||
peer->globalToLocal (newPosition),
|
||||
ModifierKeys::currentModifiers,
|
||||
0.0f,
|
||||
0.0f,
|
||||
Time::currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
double Desktop::getDefaultMasterScale()
|
||||
{
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
|
||||
{
|
||||
return upright;
|
||||
}
|
||||
|
||||
bool Desktop::isDarkModeActive() const
|
||||
{
|
||||
return [[[NSUserDefaults standardUserDefaults] stringForKey: nsStringLiteral ("AppleInterfaceStyle")]
|
||||
isEqualToString: nsStringLiteral ("Dark")];
|
||||
}
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
static const auto darkModeSelector = @selector (darkModeChanged:);
|
||||
static const auto keyboardVisibilitySelector = @selector (keyboardVisiblityChanged:);
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
class Desktop::NativeDarkModeChangeDetectorImpl
|
||||
{
|
||||
public:
|
||||
NativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
static DelegateClass delegateClass;
|
||||
delegate.reset ([delegateClass.createInstance() init]);
|
||||
observer.emplace (delegate.get(),
|
||||
darkModeSelector,
|
||||
@"AppleInterfaceThemeChangedNotification",
|
||||
nil,
|
||||
[NSDistributedNotificationCenter class]);
|
||||
}
|
||||
|
||||
private:
|
||||
struct DelegateClass final : public ObjCClass<NSObject>
|
||||
{
|
||||
DelegateClass() : ObjCClass<NSObject> ("JUCEDelegate_")
|
||||
{
|
||||
addMethod (darkModeSelector, [] (id, SEL, NSNotification*) { Desktop::getInstance().darkModeChanged(); });
|
||||
registerClass();
|
||||
}
|
||||
};
|
||||
|
||||
NSUniquePtr<NSObject> delegate;
|
||||
Optional<ScopedNotificationCenterObserver> observer;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl)
|
||||
};
|
||||
|
||||
std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
return std::make_unique<NativeDarkModeChangeDetectorImpl>();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class ScreenSaverDefeater final : public Timer
|
||||
{
|
||||
public:
|
||||
ScreenSaverDefeater()
|
||||
{
|
||||
startTimer (5000);
|
||||
timerCallback();
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
if (Process::isForegroundProcess())
|
||||
{
|
||||
if (assertion == nullptr)
|
||||
assertion.reset (new PMAssertion());
|
||||
}
|
||||
else
|
||||
{
|
||||
assertion.reset();
|
||||
}
|
||||
}
|
||||
|
||||
struct PMAssertion
|
||||
{
|
||||
PMAssertion() : assertionID (kIOPMNullAssertionID)
|
||||
{
|
||||
[[maybe_unused]] IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep,
|
||||
kIOPMAssertionLevelOn,
|
||||
CFSTR ("JUCE Playback"),
|
||||
&assertionID);
|
||||
jassert (res == kIOReturnSuccess);
|
||||
}
|
||||
|
||||
~PMAssertion()
|
||||
{
|
||||
if (assertionID != kIOPMNullAssertionID)
|
||||
IOPMAssertionRelease (assertionID);
|
||||
}
|
||||
|
||||
IOPMAssertionID assertionID;
|
||||
};
|
||||
|
||||
std::unique_ptr<PMAssertion> assertion;
|
||||
};
|
||||
|
||||
static std::unique_ptr<ScreenSaverDefeater> screenSaverDefeater;
|
||||
|
||||
void Desktop::setScreenSaverEnabled (const bool isEnabled)
|
||||
{
|
||||
if (isEnabled)
|
||||
screenSaverDefeater.reset();
|
||||
else if (screenSaverDefeater == nullptr)
|
||||
screenSaverDefeater.reset (new ScreenSaverDefeater());
|
||||
}
|
||||
|
||||
bool Desktop::isScreenSaverEnabled()
|
||||
{
|
||||
return screenSaverDefeater == nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct DisplaySettingsChangeCallback final : private DeletedAtShutdown
|
||||
{
|
||||
DisplaySettingsChangeCallback()
|
||||
{
|
||||
CGDisplayRegisterReconfigurationCallback (displayReconfigurationCallback, this);
|
||||
}
|
||||
|
||||
~DisplaySettingsChangeCallback()
|
||||
{
|
||||
CGDisplayRemoveReconfigurationCallback (displayReconfigurationCallback, this);
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
static void displayReconfigurationCallback (CGDirectDisplayID, CGDisplayChangeSummaryFlags, void* userInfo)
|
||||
{
|
||||
if (auto* thisPtr = static_cast<DisplaySettingsChangeCallback*> (userInfo))
|
||||
NullCheckedInvocation::invoke (thisPtr->forceDisplayUpdate);
|
||||
}
|
||||
|
||||
std::function<void()> forceDisplayUpdate;
|
||||
|
||||
JUCE_DECLARE_SINGLETON (DisplaySettingsChangeCallback, false)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DisplaySettingsChangeCallback)
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (DisplaySettingsChangeCallback)
|
||||
|
||||
static Rectangle<int> convertDisplayRect (NSRect r, CGFloat mainScreenBottom)
|
||||
{
|
||||
r.origin.y = mainScreenBottom - (r.origin.y + r.size.height);
|
||||
return convertToRectInt (r);
|
||||
}
|
||||
|
||||
static Displays::Display getDisplayFromScreen (NSScreen* s, CGFloat& mainScreenBottom, const float masterScale)
|
||||
{
|
||||
Displays::Display d;
|
||||
|
||||
d.isMain = (approximatelyEqual (mainScreenBottom, 0.0));
|
||||
|
||||
if (d.isMain)
|
||||
mainScreenBottom = [s frame].size.height;
|
||||
|
||||
d.userArea = convertDisplayRect ([s visibleFrame], mainScreenBottom) / masterScale;
|
||||
d.totalArea = convertDisplayRect ([s frame], mainScreenBottom) / masterScale;
|
||||
d.scale = masterScale;
|
||||
|
||||
if ([s respondsToSelector: @selector (backingScaleFactor)])
|
||||
d.scale *= s.backingScaleFactor;
|
||||
|
||||
NSSize dpi = [[[s deviceDescription] objectForKey: NSDeviceResolution] sizeValue];
|
||||
d.dpi = (dpi.width + dpi.height) / 2.0;
|
||||
|
||||
#if defined (MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
if (@available (macOS 12.0, *))
|
||||
{
|
||||
const auto safeInsets = [s safeAreaInsets];
|
||||
d.safeAreaInsets = detail::WindowingHelpers::roundToInt (BorderSize<double> { safeInsets.top,
|
||||
safeInsets.left,
|
||||
safeInsets.bottom,
|
||||
safeInsets.right }.multipliedBy (1.0 / (double) masterScale));
|
||||
}
|
||||
#endif
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void Displays::findDisplays (const float masterScale)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
if (DisplaySettingsChangeCallback::getInstanceWithoutCreating() == nullptr)
|
||||
DisplaySettingsChangeCallback::getInstance()->forceDisplayUpdate = [this] { refresh(); };
|
||||
|
||||
CGFloat mainScreenBottom = 0;
|
||||
|
||||
for (NSScreen* s in [NSScreen screens])
|
||||
displays.add (getDisplayFromScreen (s, mainScreenBottom, masterScale));
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static void selectImageForDrawing (const Image& image)
|
||||
{
|
||||
[NSGraphicsContext saveGraphicsState];
|
||||
|
||||
if (@available (macOS 10.10, *))
|
||||
{
|
||||
[NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithCGContext: juce_getImageContext (image)
|
||||
flipped: false]];
|
||||
return;
|
||||
}
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
[NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image)
|
||||
flipped: false]];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
static void releaseImageAfterDrawing()
|
||||
{
|
||||
[[NSGraphicsContext currentContext] flushGraphics];
|
||||
[NSGraphicsContext restoreGraphicsState];
|
||||
}
|
||||
|
||||
Image detail::WindowingHelpers::createIconForFile (const File& file)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (file.getFullPathName())];
|
||||
|
||||
Image result (Image::ARGB, (int) [image size].width, (int) [image size].height, true);
|
||||
|
||||
selectImageForDrawing (result);
|
||||
[image drawAtPoint: NSMakePoint (0, 0)
|
||||
fromRect: NSMakeRect (0, 0, [image size].width, [image size].height)
|
||||
operation: NSCompositingOperationSourceOver fraction: 1.0f];
|
||||
releaseImageAfterDrawing();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static Image createNSWindowSnapshot (NSWindow* nsWindow)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
// CGWindowListCreateImage is replaced by functions in the ScreenCaptureKit framework, but
|
||||
// that framework is only available from macOS 12.3 onwards.
|
||||
// A suitable @available check should be added once the minimum build OS is 12.3 or greater,
|
||||
// so that ScreenCaptureKit can be weak-linked.
|
||||
#if defined (MAC_OS_VERSION_14_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
#define JUCE_DEPRECATION_IGNORED 1
|
||||
#endif
|
||||
|
||||
CGImageRef screenShot = CGWindowListCreateImage (CGRectNull,
|
||||
kCGWindowListOptionIncludingWindow,
|
||||
(CGWindowID) [nsWindow windowNumber],
|
||||
kCGWindowImageBoundsIgnoreFraming);
|
||||
|
||||
#if JUCE_DEPRECATION_IGNORED
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
#undef JUCE_DEPRECATION_IGNORED
|
||||
#endif
|
||||
|
||||
NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage: screenShot];
|
||||
|
||||
Image result (Image::ARGB, (int) [bitmapRep size].width, (int) [bitmapRep size].height, true);
|
||||
|
||||
selectImageForDrawing (result);
|
||||
[bitmapRep drawAtPoint: NSMakePoint (0, 0)];
|
||||
releaseImageAfterDrawing();
|
||||
|
||||
[bitmapRep release];
|
||||
CGImageRelease (screenShot);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Image createSnapshotOfNativeWindow (void* nativeWindowHandle)
|
||||
{
|
||||
if (id windowOrView = (id) nativeWindowHandle)
|
||||
{
|
||||
if ([windowOrView isKindOfClass: [NSWindow class]])
|
||||
return createNSWindowSnapshot ((NSWindow*) windowOrView);
|
||||
|
||||
if ([windowOrView isKindOfClass: [NSView class]])
|
||||
return createNSWindowSnapshot ([(NSView*) windowOrView window]);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SystemClipboard::copyTextToClipboard (const String& text)
|
||||
{
|
||||
NSPasteboard* pb = [NSPasteboard generalPasteboard];
|
||||
|
||||
[pb declareTypes: [NSArray arrayWithObject: NSPasteboardTypeString]
|
||||
owner: nil];
|
||||
|
||||
[pb setString: juceStringToNS (text)
|
||||
forType: NSPasteboardTypeString];
|
||||
}
|
||||
|
||||
String SystemClipboard::getTextFromClipboard()
|
||||
{
|
||||
return nsStringToJuce ([[NSPasteboard generalPasteboard] stringForType: NSPasteboardTypeString]);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
File diff suppressed because it is too large
Load Diff
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
class WindowsHooks::Hooks
|
||||
{
|
||||
public:
|
||||
Hooks() = default;
|
||||
|
||||
~Hooks()
|
||||
{
|
||||
if (mouseWheelHook != nullptr)
|
||||
UnhookWindowsHookEx (mouseWheelHook);
|
||||
|
||||
if (keyboardHook != nullptr)
|
||||
UnhookWindowsHookEx (keyboardHook);
|
||||
}
|
||||
|
||||
static inline std::weak_ptr<Hooks> weak;
|
||||
|
||||
private:
|
||||
static LRESULT CALLBACK mouseWheelHookCallback (int nCode, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (nCode >= 0 && wParam == WM_MOUSEWHEEL)
|
||||
{
|
||||
// using a local copy of this struct to support old mingw libraries
|
||||
struct MOUSEHOOKSTRUCTEX_ final : public MOUSEHOOKSTRUCT { DWORD mouseData; };
|
||||
|
||||
auto& hs = *(MOUSEHOOKSTRUCTEX_*) lParam;
|
||||
|
||||
if (auto* comp = Desktop::getInstance().findComponentAt ({ hs.pt.x, hs.pt.y }))
|
||||
{
|
||||
if (auto* target = static_cast<HWND> (comp->getWindowHandle()))
|
||||
{
|
||||
const ScopedThreadDPIAwarenessSetter scope { target };
|
||||
return PostMessage (target, WM_MOUSEWHEEL,
|
||||
hs.mouseData & 0xffff0000, MAKELPARAM (hs.pt.x, hs.pt.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CallNextHookEx (getSingleton()->mouseWheelHook, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK keyboardHookCallback (int nCode, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
auto& msg = *reinterpret_cast<MSG*> (lParam);
|
||||
|
||||
if (nCode == HC_ACTION && wParam == PM_REMOVE && HWNDComponentPeer::offerKeyMessageToJUCEWindow (msg))
|
||||
{
|
||||
msg = {};
|
||||
msg.message = WM_USER;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return CallNextHookEx (getSingleton()->keyboardHook, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
HHOOK mouseWheelHook = SetWindowsHookEx (WH_MOUSE,
|
||||
mouseWheelHookCallback,
|
||||
(HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(),
|
||||
GetCurrentThreadId());
|
||||
HHOOK keyboardHook = SetWindowsHookEx (WH_GETMESSAGE,
|
||||
keyboardHookCallback,
|
||||
(HINSTANCE) juce::Process::getCurrentModuleInstanceHandle(),
|
||||
GetCurrentThreadId());
|
||||
};
|
||||
|
||||
auto WindowsHooks::getSingleton() -> std::shared_ptr<Hooks>
|
||||
{
|
||||
auto& weak = Hooks::weak;
|
||||
|
||||
if (auto locked = weak.lock())
|
||||
return locked;
|
||||
|
||||
auto strong = std::make_shared<Hooks>();
|
||||
weak = strong;
|
||||
return strong;
|
||||
}
|
||||
|
||||
} // namespace juce::detail
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
|
||||
namespace juce::detail
|
||||
{
|
||||
|
||||
class WindowsHooks
|
||||
{
|
||||
public:
|
||||
WindowsHooks() = default;
|
||||
|
||||
private:
|
||||
class Hooks;
|
||||
|
||||
static std::shared_ptr<Hooks> getSingleton();
|
||||
|
||||
std::shared_ptr<Hooks> hooks = getSingleton();
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace X11SymbolHelpers
|
||||
{
|
||||
|
||||
template <typename FuncPtr>
|
||||
struct SymbolBinding
|
||||
{
|
||||
FuncPtr& func;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
template <typename FuncPtr>
|
||||
SymbolBinding<FuncPtr> makeSymbolBinding (FuncPtr& func, const char* name)
|
||||
{
|
||||
return { func, name };
|
||||
}
|
||||
|
||||
template <typename FuncPtr>
|
||||
bool loadSymbols (DynamicLibrary& lib, SymbolBinding<FuncPtr> binding)
|
||||
{
|
||||
if (auto* func = lib.getFunction (binding.name))
|
||||
{
|
||||
binding.func = reinterpret_cast<FuncPtr> (func);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename FuncPtr, typename... Args>
|
||||
bool loadSymbols (DynamicLibrary& lib1, DynamicLibrary& lib2, SymbolBinding<FuncPtr> binding)
|
||||
{
|
||||
return loadSymbols (lib1, binding) || loadSymbols (lib2, binding);
|
||||
}
|
||||
|
||||
template <typename FuncPtr, typename... Args>
|
||||
bool loadSymbols (DynamicLibrary& lib, SymbolBinding<FuncPtr> binding, Args... args)
|
||||
{
|
||||
return loadSymbols (lib, binding) && loadSymbols (lib, args...);
|
||||
}
|
||||
|
||||
template <typename FuncPtr, typename... Args>
|
||||
bool loadSymbols (DynamicLibrary& lib1, DynamicLibrary& lib2, SymbolBinding<FuncPtr> binding, Args... args)
|
||||
{
|
||||
return loadSymbols (lib1, lib2, binding) && loadSymbols (lib1, lib2, args...);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool X11Symbols::loadAllSymbols()
|
||||
{
|
||||
using namespace X11SymbolHelpers;
|
||||
|
||||
if (! loadSymbols (xLib, xextLib,
|
||||
makeSymbolBinding (xAllocClassHint, "XAllocClassHint"),
|
||||
makeSymbolBinding (xAllocSizeHints, "XAllocSizeHints"),
|
||||
makeSymbolBinding (xAllocWMHints, "XAllocWMHints"),
|
||||
makeSymbolBinding (xBitmapBitOrder, "XBitmapBitOrder"),
|
||||
makeSymbolBinding (xBitmapUnit, "XBitmapUnit"),
|
||||
makeSymbolBinding (xChangeActivePointerGrab, "XChangeActivePointerGrab"),
|
||||
makeSymbolBinding (xChangeProperty, "XChangeProperty"),
|
||||
makeSymbolBinding (xCheckTypedWindowEvent, "XCheckTypedWindowEvent"),
|
||||
makeSymbolBinding (xCheckWindowEvent, "XCheckWindowEvent"),
|
||||
makeSymbolBinding (xClearArea, "XClearArea"),
|
||||
makeSymbolBinding (xCloseDisplay, "XCloseDisplay"),
|
||||
makeSymbolBinding (xConnectionNumber, "XConnectionNumber"),
|
||||
makeSymbolBinding (xConvertSelection, "XConvertSelection"),
|
||||
makeSymbolBinding (xCreateColormap, "XCreateColormap"),
|
||||
makeSymbolBinding (xCreateFontCursor, "XCreateFontCursor"),
|
||||
makeSymbolBinding (xCreateGC, "XCreateGC"),
|
||||
makeSymbolBinding (xCreateImage, "XCreateImage"),
|
||||
makeSymbolBinding (xCreatePixmap, "XCreatePixmap"),
|
||||
makeSymbolBinding (xCreatePixmapCursor, "XCreatePixmapCursor"),
|
||||
makeSymbolBinding (xCreatePixmapFromBitmapData, "XCreatePixmapFromBitmapData"),
|
||||
makeSymbolBinding (xCreateWindow, "XCreateWindow"),
|
||||
makeSymbolBinding (xDefaultRootWindow, "XDefaultRootWindow"),
|
||||
makeSymbolBinding (xDefaultScreen, "XDefaultScreen"),
|
||||
makeSymbolBinding (xDefaultScreenOfDisplay, "XDefaultScreenOfDisplay"),
|
||||
makeSymbolBinding (xDefaultVisual, "XDefaultVisual"),
|
||||
makeSymbolBinding (xDefineCursor, "XDefineCursor"),
|
||||
makeSymbolBinding (xDeleteContext, "XDeleteContext"),
|
||||
makeSymbolBinding (xDeleteProperty, "XDeleteProperty"),
|
||||
makeSymbolBinding (xDestroyImage, "XDestroyImage"),
|
||||
makeSymbolBinding (xDestroyWindow, "XDestroyWindow"),
|
||||
makeSymbolBinding (xDisplayHeight, "XDisplayHeight"),
|
||||
makeSymbolBinding (xDisplayHeightMM, "XDisplayHeightMM"),
|
||||
makeSymbolBinding (xDisplayWidth, "XDisplayWidth"),
|
||||
makeSymbolBinding (xDisplayWidthMM, "XDisplayWidthMM"),
|
||||
makeSymbolBinding (xEventsQueued, "XEventsQueued"),
|
||||
makeSymbolBinding (xFindContext, "XFindContext"),
|
||||
makeSymbolBinding (xFlush, "XFlush"),
|
||||
makeSymbolBinding (xFree, "XFree"),
|
||||
makeSymbolBinding (xFreeCursor, "XFreeCursor"),
|
||||
makeSymbolBinding (xFreeColormap, "XFreeColormap"),
|
||||
makeSymbolBinding (xFreeGC, "XFreeGC"),
|
||||
makeSymbolBinding (xFreeModifiermap, "XFreeModifiermap"),
|
||||
makeSymbolBinding (xFreePixmap, "XFreePixmap"),
|
||||
makeSymbolBinding (xGetAtomName, "XGetAtomName"),
|
||||
makeSymbolBinding (xGetErrorDatabaseText, "XGetErrorDatabaseText"),
|
||||
makeSymbolBinding (xGetErrorText, "XGetErrorText"),
|
||||
makeSymbolBinding (xGetGeometry, "XGetGeometry"),
|
||||
makeSymbolBinding (xGetImage, "XGetImage"),
|
||||
makeSymbolBinding (xGetInputFocus, "XGetInputFocus"),
|
||||
makeSymbolBinding (xGetModifierMapping, "XGetModifierMapping"),
|
||||
makeSymbolBinding (xGetPointerMapping, "XGetPointerMapping"),
|
||||
makeSymbolBinding (xGetSelectionOwner, "XGetSelectionOwner"),
|
||||
makeSymbolBinding (xGetVisualInfo, "XGetVisualInfo"),
|
||||
makeSymbolBinding (xGetWMHints, "XGetWMHints"),
|
||||
makeSymbolBinding (xGetWindowAttributes, "XGetWindowAttributes"),
|
||||
makeSymbolBinding (xGetWindowProperty, "XGetWindowProperty"),
|
||||
makeSymbolBinding (xGrabPointer, "XGrabPointer"),
|
||||
makeSymbolBinding (xGrabServer, "XGrabServer"),
|
||||
makeSymbolBinding (xImageByteOrder, "XImageByteOrder"),
|
||||
makeSymbolBinding (xInitImage, "XInitImage"),
|
||||
makeSymbolBinding (xInitThreads, "XInitThreads"),
|
||||
makeSymbolBinding (xInstallColormap, "XInstallColormap"),
|
||||
makeSymbolBinding (xInternAtom, "XInternAtom"),
|
||||
makeSymbolBinding (xkbKeycodeToKeysym, "XkbKeycodeToKeysym"),
|
||||
makeSymbolBinding (xKeysymToKeycode, "XKeysymToKeycode"),
|
||||
makeSymbolBinding (xListProperties, "XListProperties"),
|
||||
makeSymbolBinding (xLockDisplay, "XLockDisplay"),
|
||||
makeSymbolBinding (xLookupString, "XLookupString"),
|
||||
makeSymbolBinding (xMapRaised, "XMapRaised"),
|
||||
makeSymbolBinding (xMapWindow, "XMapWindow"),
|
||||
makeSymbolBinding (xMoveResizeWindow, "XMoveResizeWindow"),
|
||||
makeSymbolBinding (xNextEvent, "XNextEvent"),
|
||||
makeSymbolBinding (xOpenDisplay, "XOpenDisplay"),
|
||||
makeSymbolBinding (xPeekEvent, "XPeekEvent"),
|
||||
makeSymbolBinding (xPending, "XPending"),
|
||||
makeSymbolBinding (xPutImage, "XPutImage"),
|
||||
makeSymbolBinding (xPutPixel, "XPutPixel"),
|
||||
makeSymbolBinding (xQueryBestCursor, "XQueryBestCursor"),
|
||||
makeSymbolBinding (xQueryExtension, "XQueryExtension"),
|
||||
makeSymbolBinding (xQueryPointer, "XQueryPointer"),
|
||||
makeSymbolBinding (xQueryTree, "XQueryTree"),
|
||||
makeSymbolBinding (xRefreshKeyboardMapping, "XRefreshKeyboardMapping"),
|
||||
makeSymbolBinding (xReparentWindow, "XReparentWindow"),
|
||||
makeSymbolBinding (xResizeWindow, "XResizeWindow"),
|
||||
makeSymbolBinding (xRestackWindows, "XRestackWindows"),
|
||||
makeSymbolBinding (xRootWindow, "XRootWindow"),
|
||||
makeSymbolBinding (xSaveContext, "XSaveContext"),
|
||||
makeSymbolBinding (xScreenCount, "XScreenCount"),
|
||||
makeSymbolBinding (xScreenNumberOfScreen, "XScreenNumberOfScreen"),
|
||||
makeSymbolBinding (xSelectInput, "XSelectInput"),
|
||||
makeSymbolBinding (xSendEvent, "XSendEvent"),
|
||||
makeSymbolBinding (xSetClassHint, "XSetClassHint"),
|
||||
makeSymbolBinding (xSetErrorHandler, "XSetErrorHandler"),
|
||||
makeSymbolBinding (xSetIOErrorHandler, "XSetIOErrorHandler"),
|
||||
makeSymbolBinding (xSetInputFocus, "XSetInputFocus"),
|
||||
makeSymbolBinding (xSetSelectionOwner, "XSetSelectionOwner"),
|
||||
makeSymbolBinding (xSetWMHints, "XSetWMHints"),
|
||||
makeSymbolBinding (xSetWMIconName, "XSetWMIconName"),
|
||||
makeSymbolBinding (xSetWMName, "XSetWMName"),
|
||||
makeSymbolBinding (xSetWMNormalHints, "XSetWMNormalHints"),
|
||||
makeSymbolBinding (xStringListToTextProperty, "XStringListToTextProperty"),
|
||||
makeSymbolBinding (xSync, "XSync"),
|
||||
makeSymbolBinding (xSynchronize, "XSynchronize"),
|
||||
makeSymbolBinding (xTranslateCoordinates, "XTranslateCoordinates"),
|
||||
makeSymbolBinding (xrmUniqueQuark, "XrmUniqueQuark"),
|
||||
makeSymbolBinding (xUngrabPointer, "XUngrabPointer"),
|
||||
makeSymbolBinding (xUngrabServer, "XUngrabServer"),
|
||||
makeSymbolBinding (xUnlockDisplay, "XUnlockDisplay"),
|
||||
makeSymbolBinding (xUnmapWindow, "XUnmapWindow"),
|
||||
makeSymbolBinding (xutf8TextListToTextProperty, "Xutf8TextListToTextProperty"),
|
||||
makeSymbolBinding (xWarpPointer, "XWarpPointer")))
|
||||
return false;
|
||||
|
||||
#if JUCE_USE_XCURSOR
|
||||
loadSymbols (xcursorLib,
|
||||
makeSymbolBinding (xcursorImageCreate, "XcursorImageCreate"),
|
||||
makeSymbolBinding (xcursorImageLoadCursor, "XcursorImageLoadCursor"),
|
||||
makeSymbolBinding (xcursorImageDestroy, "XcursorImageDestroy"));
|
||||
#endif
|
||||
#if JUCE_USE_XINERAMA
|
||||
loadSymbols (xineramaLib,
|
||||
makeSymbolBinding (xineramaIsActive, "XineramaIsActive"),
|
||||
makeSymbolBinding (xineramaQueryScreens, "XineramaQueryScreens"));
|
||||
#endif
|
||||
#if JUCE_USE_XRENDER
|
||||
loadSymbols (xrenderLib,
|
||||
makeSymbolBinding (xRenderQueryVersion, "XRenderQueryVersion"),
|
||||
makeSymbolBinding (xRenderFindStandardFormat, "XRenderFindStandardFormat"),
|
||||
makeSymbolBinding (xRenderFindFormat, "XRenderFindFormat"),
|
||||
makeSymbolBinding (xRenderFindVisualFormat, "XRenderFindVisualFormat"));
|
||||
#endif
|
||||
#if JUCE_USE_XRANDR
|
||||
loadSymbols (xrandrLib,
|
||||
makeSymbolBinding (xRRGetScreenResources, "XRRGetScreenResources"),
|
||||
makeSymbolBinding (xRRFreeScreenResources, "XRRFreeScreenResources"),
|
||||
makeSymbolBinding (xRRGetOutputInfo, "XRRGetOutputInfo"),
|
||||
makeSymbolBinding (xRRFreeOutputInfo, "XRRFreeOutputInfo"),
|
||||
makeSymbolBinding (xRRGetCrtcInfo, "XRRGetCrtcInfo"),
|
||||
makeSymbolBinding (xRRFreeCrtcInfo, "XRRFreeCrtcInfo"),
|
||||
makeSymbolBinding (xRRGetOutputPrimary, "XRRGetOutputPrimary"));
|
||||
#endif
|
||||
#if JUCE_USE_XSHM
|
||||
loadSymbols (xLib, xextLib,
|
||||
makeSymbolBinding (xShmAttach, "XShmAttach"),
|
||||
makeSymbolBinding (xShmCreateImage, "XShmCreateImage"),
|
||||
makeSymbolBinding (xShmDetach, "XShmDetach"),
|
||||
makeSymbolBinding (xShmGetEventBase, "XShmGetEventBase"),
|
||||
makeSymbolBinding (xShmPutImage, "XShmPutImage"),
|
||||
makeSymbolBinding (xShmQueryVersion, "XShmQueryVersion"));
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_IMPLEMENT_SINGLETON (X11Symbols)
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,620 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
namespace ReturnHelpers
|
||||
{
|
||||
template <typename Type>
|
||||
Type returnDefaultConstructedAnyType() { return {}; }
|
||||
|
||||
template <>
|
||||
inline void returnDefaultConstructedAnyType<void>() {}
|
||||
}
|
||||
|
||||
#define JUCE_GENERATE_FUNCTION_WITH_DEFAULT(functionName, objectName, args, returnType) \
|
||||
using functionName = returnType (*) args; \
|
||||
functionName objectName = [] args -> returnType { return ReturnHelpers::returnDefaultConstructedAnyType<returnType>(); };
|
||||
|
||||
|
||||
//==============================================================================
|
||||
class JUCE_API X11Symbols
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
bool loadAllSymbols();
|
||||
|
||||
//==============================================================================
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XAllocClassHint, xAllocClassHint,
|
||||
(),
|
||||
XClassHint*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XAllocSizeHints, xAllocSizeHints,
|
||||
(),
|
||||
XSizeHints*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XAllocWMHints, xAllocWMHints,
|
||||
(),
|
||||
XWMHints*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XBitmapBitOrder, xBitmapBitOrder,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XBitmapUnit, xBitmapUnit,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XChangeActivePointerGrab, xChangeActivePointerGrab,
|
||||
(::Display*, unsigned int, Cursor, ::Time),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XChangeProperty, xChangeProperty,
|
||||
(::Display*, ::Window, Atom, Atom, int, int, const unsigned char*, int),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCheckTypedWindowEvent, xCheckTypedWindowEvent,
|
||||
(::Display*, ::Window, int, XEvent*),
|
||||
Bool)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCheckWindowEvent, xCheckWindowEvent,
|
||||
(::Display*, ::Window, long, XEvent*),
|
||||
Bool)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XClearArea, xClearArea,
|
||||
(::Display*, ::Window, int, int, unsigned int, unsigned int, Bool),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCloseDisplay, xCloseDisplay,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XConnectionNumber, xConnectionNumber,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XConvertSelection, xConvertSelection,
|
||||
(::Display*, Atom, Atom, Atom, ::Window, ::Time),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCreateColormap, xCreateColormap,
|
||||
(::Display*, ::Window, Visual*, int),
|
||||
Colormap)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCreateFontCursor, xCreateFontCursor,
|
||||
(::Display*, unsigned int),
|
||||
Cursor)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCreateGC, xCreateGC,
|
||||
(::Display*, ::Drawable, unsigned long, XGCValues*),
|
||||
GC)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCreateImage, xCreateImage,
|
||||
(::Display*, Visual*, unsigned int, int, int, const char*, unsigned int, unsigned int, int, int),
|
||||
XImage*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCreatePixmap, xCreatePixmap,
|
||||
(::Display*, ::Drawable, unsigned int, unsigned int, unsigned int),
|
||||
Pixmap)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCreatePixmapCursor, xCreatePixmapCursor,
|
||||
(::Display*, Pixmap, Pixmap, XColor*, XColor*, unsigned int, unsigned int),
|
||||
Cursor)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCreatePixmapFromBitmapData, xCreatePixmapFromBitmapData,
|
||||
(::Display*, ::Drawable, const char*, unsigned int, unsigned int, unsigned long, unsigned long, unsigned int),
|
||||
Pixmap)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XCreateWindow, xCreateWindow,
|
||||
(::Display*, ::Window, int, int, unsigned int, unsigned int, unsigned int, int, unsigned int, Visual*, unsigned long, XSetWindowAttributes*),
|
||||
::Window)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDefaultRootWindow, xDefaultRootWindow,
|
||||
(::Display*),
|
||||
::Window)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDefaultScreen, xDefaultScreen,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDefaultScreenOfDisplay, xDefaultScreenOfDisplay,
|
||||
(::Display*),
|
||||
Screen*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDefaultVisual, xDefaultVisual,
|
||||
(::Display*, int),
|
||||
Visual*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDefineCursor, xDefineCursor,
|
||||
(::Display*, ::Window, Cursor),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDeleteContext, xDeleteContext,
|
||||
(::Display*, XID, XContext),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDeleteProperty, xDeleteProperty,
|
||||
(::Display*, Window, Atom),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDestroyImage, xDestroyImage,
|
||||
(XImage*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDestroyWindow, xDestroyWindow,
|
||||
(::Display*, ::Window),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDisplayHeight, xDisplayHeight,
|
||||
(::Display*, int),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDisplayHeightMM, xDisplayHeightMM,
|
||||
(::Display*, int),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDisplayWidth, xDisplayWidth,
|
||||
(::Display*, int),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XDisplayWidthMM, xDisplayWidthMM,
|
||||
(::Display*, int),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XEventsQueued, xEventsQueued,
|
||||
(::Display*, int),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XFindContext, xFindContext,
|
||||
(::Display*, XID, XContext, XPointer*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XFlush, xFlush,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XFree, xFree,
|
||||
(void*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XFreeCursor, xFreeCursor,
|
||||
(::Display*, Cursor),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XFreeColormap ,xFreeColormap,
|
||||
(::Display*, Colormap),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XFreeGC, xFreeGC,
|
||||
(::Display*, GC),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XFreeModifiermap, xFreeModifiermap,
|
||||
(XModifierKeymap*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XFreePixmap, xFreePixmap,
|
||||
(::Display*, Pixmap),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetAtomName, xGetAtomName,
|
||||
(::Display*, Atom),
|
||||
char*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetErrorDatabaseText, xGetErrorDatabaseText,
|
||||
(::Display*, const char*, const char*, const char*, const char*, int),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetErrorText, xGetErrorText,
|
||||
(::Display*, int, const char*, int),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetGeometry, xGetGeometry,
|
||||
(::Display*, ::Drawable, ::Window*, int*, int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*),
|
||||
Status)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetImage, xGetImage,
|
||||
(::Display*, ::Drawable, int, int, unsigned int, unsigned int, unsigned long, int),
|
||||
XImage*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetInputFocus, xGetInputFocus,
|
||||
(::Display*, ::Window*, int*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetModifierMapping, xGetModifierMapping,
|
||||
(::Display*),
|
||||
XModifierKeymap*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetPointerMapping, xGetPointerMapping,
|
||||
(::Display*, unsigned char[], int),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetSelectionOwner, xGetSelectionOwner,
|
||||
(::Display*, Atom),
|
||||
::Window)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetVisualInfo, xGetVisualInfo,
|
||||
(::Display*, long, XVisualInfo*, int*),
|
||||
XVisualInfo*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetWMHints, xGetWMHints,
|
||||
(::Display*, ::Window),
|
||||
XWMHints*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetWindowAttributes, xGetWindowAttributes,
|
||||
(::Display*, ::Window, XWindowAttributes*),
|
||||
Status)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGetWindowProperty, xGetWindowProperty,
|
||||
(::Display*, ::Window, Atom, long, long, Bool, Atom, Atom*, int*, unsigned long*, unsigned long*, unsigned char**),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGrabPointer, xGrabPointer,
|
||||
(::Display*, ::Window, Bool, unsigned int, int, int, ::Window, Cursor, ::Time),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XGrabServer, xGrabServer,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XImageByteOrder, xImageByteOrder,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XInitImage, xInitImage,
|
||||
(XImage*),
|
||||
Status)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XInitThreads, xInitThreads,
|
||||
(),
|
||||
Status)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XInstallColormap, xInstallColormap,
|
||||
(::Display*, Colormap),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XInternAtom, xInternAtom,
|
||||
(::Display*, const char*, Bool),
|
||||
Atom)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XkbKeycodeToKeysym, xkbKeycodeToKeysym,
|
||||
(::Display*, KeyCode, unsigned int, unsigned int),
|
||||
KeySym)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XKeysymToKeycode, xKeysymToKeycode,
|
||||
(::Display*, KeySym),
|
||||
KeyCode)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XListProperties, xListProperties,
|
||||
(::Display*, Window, int*),
|
||||
Atom*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XLockDisplay, xLockDisplay,
|
||||
(::Display*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XLookupString, xLookupString,
|
||||
(XKeyEvent*, const char*, int, KeySym*, XComposeStatus*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XMapRaised, xMapRaised,
|
||||
(::Display*, ::Window),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XMapWindow, xMapWindow,
|
||||
(::Display*, ::Window),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XMoveResizeWindow, xMoveResizeWindow,
|
||||
(::Display*, ::Window, int, int, unsigned int, unsigned int),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XNextEvent, xNextEvent,
|
||||
(::Display*, XEvent*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XOpenDisplay, xOpenDisplay,
|
||||
(const char*),
|
||||
::Display*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XPeekEvent, xPeekEvent,
|
||||
(::Display*, XEvent*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XPending, xPending,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XPutImage, xPutImage,
|
||||
(::Display*, ::Drawable, GC, XImage*, int, int, int, int, unsigned int, unsigned int),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XPutPixel, xPutPixel,
|
||||
(XImage*, int, int, unsigned long),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XQueryBestCursor, xQueryBestCursor,
|
||||
(::Display*, ::Drawable, unsigned int, unsigned int, unsigned int*, unsigned int*),
|
||||
Status)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XQueryExtension, xQueryExtension,
|
||||
(::Display*, const char*, int*, int*, int*),
|
||||
Bool)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XQueryPointer, xQueryPointer,
|
||||
(::Display*, ::Window, ::Window*, ::Window*, int*, int*, int*, int*, unsigned int*),
|
||||
Bool)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XQueryTree, xQueryTree,
|
||||
(::Display*, ::Window, ::Window*, ::Window*, ::Window**, unsigned int*),
|
||||
Status)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRefreshKeyboardMapping, xRefreshKeyboardMapping,
|
||||
(XMappingEvent*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XReparentWindow, xReparentWindow,
|
||||
(::Display*, ::Window, ::Window, int, int),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XResizeWindow, xResizeWindow,
|
||||
(::Display*, Window, unsigned int, unsigned int),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRestackWindows, xRestackWindows,
|
||||
(::Display*, ::Window[], int),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRootWindow, xRootWindow,
|
||||
(::Display*, int),
|
||||
::Window)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSaveContext, xSaveContext,
|
||||
(::Display*, XID, XContext, XPointer),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XScreenCount, xScreenCount,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XScreenNumberOfScreen, xScreenNumberOfScreen,
|
||||
(Screen*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSelectInput, xSelectInput,
|
||||
(::Display*, ::Window, long),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSendEvent, xSendEvent,
|
||||
(::Display*, ::Window, Bool, long, XEvent*),
|
||||
Status)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetClassHint, xSetClassHint,
|
||||
(::Display*, ::Window, XClassHint*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetErrorHandler, xSetErrorHandler,
|
||||
(XErrorHandler),
|
||||
XErrorHandler)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetIOErrorHandler, xSetIOErrorHandler,
|
||||
(XIOErrorHandler),
|
||||
XIOErrorHandler)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetInputFocus, xSetInputFocus,
|
||||
(::Display*, ::Window, int, ::Time),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetSelectionOwner, xSetSelectionOwner,
|
||||
(::Display*, Atom, ::Window, ::Time),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetWMHints, xSetWMHints,
|
||||
(::Display*, ::Window, XWMHints*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetWMIconName, xSetWMIconName,
|
||||
(::Display*, ::Window, XTextProperty*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetWMName, xSetWMName,
|
||||
(::Display*, ::Window, XTextProperty*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetWMNormalHints, xSetWMNormalHints,
|
||||
(::Display*, ::Window, XSizeHints*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XStringListToTextProperty, xStringListToTextProperty,
|
||||
(char**, int, XTextProperty*),
|
||||
Status)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (Xutf8TextListToTextProperty, xutf8TextListToTextProperty,
|
||||
(::Display*, char**, int, XICCEncodingStyle, XTextProperty*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSync, xSync,
|
||||
(::Display*, Bool),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSynchronize, xSynchronize,
|
||||
(::Display*, Bool),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XTranslateCoordinates, xTranslateCoordinates,
|
||||
(::Display*, ::Window, ::Window, int, int, int*, int*, ::Window*),
|
||||
Bool)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XrmUniqueQuark, xrmUniqueQuark,
|
||||
(),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XUngrabPointer, xUngrabPointer,
|
||||
(::Display*, ::Time),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XUngrabServer, xUngrabServer,
|
||||
(::Display*),
|
||||
int)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XUnlockDisplay, xUnlockDisplay,
|
||||
(::Display*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XUnmapWindow, xUnmapWindow,
|
||||
(::Display*, ::Window),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XWarpPointer, xWarpPointer,
|
||||
(::Display*, ::Window, ::Window, int, int, unsigned int, unsigned int, int, int),
|
||||
void)
|
||||
#if JUCE_USE_XCURSOR
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XcursorImageCreate, xcursorImageCreate,
|
||||
(int, int),
|
||||
XcursorImage*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XcursorImageLoadCursor, xcursorImageLoadCursor,
|
||||
(::Display*, XcursorImage*),
|
||||
Cursor)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XcursorImageDestroy, xcursorImageDestroy,
|
||||
(XcursorImage*),
|
||||
void)
|
||||
#endif
|
||||
#if JUCE_USE_XINERAMA
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XineramaIsActive, xineramaIsActive,
|
||||
(::Display*),
|
||||
Bool)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XineramaQueryScreens, xineramaQueryScreens,
|
||||
(::Display*, int*),
|
||||
XineramaScreenInfo*)
|
||||
#endif
|
||||
#if JUCE_USE_XRENDER
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRenderQueryVersion, xRenderQueryVersion,
|
||||
(::Display*, int*, int*),
|
||||
Status)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRenderFindStandardFormat, xRenderFindStandardFormat,
|
||||
(Display*, int),
|
||||
XRenderPictFormat*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRenderFindFormat, xRenderFindFormat,
|
||||
(Display*, unsigned long, XRenderPictFormat*, int),
|
||||
XRenderPictFormat*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRenderFindVisualFormat, xRenderFindVisualFormat,
|
||||
(Display*, Visual*),
|
||||
XRenderPictFormat*)
|
||||
#endif
|
||||
#if JUCE_USE_XRANDR
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRRGetScreenResources, xRRGetScreenResources,
|
||||
(::Display*, Window),
|
||||
XRRScreenResources*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRRFreeScreenResources, xRRFreeScreenResources,
|
||||
(XRRScreenResources*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRRGetOutputInfo, xRRGetOutputInfo,
|
||||
(::Display*, XRRScreenResources*, RROutput),
|
||||
XRROutputInfo*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRRFreeOutputInfo, xRRFreeOutputInfo,
|
||||
(XRROutputInfo*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRRGetCrtcInfo, xRRGetCrtcInfo,
|
||||
(::Display*, XRRScreenResources*, RRCrtc),
|
||||
XRRCrtcInfo*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRRFreeCrtcInfo, xRRFreeCrtcInfo,
|
||||
(XRRCrtcInfo*),
|
||||
void)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XRRGetOutputPrimary, xRRGetOutputPrimary,
|
||||
(::Display*, ::Window),
|
||||
RROutput)
|
||||
#endif
|
||||
#if JUCE_USE_XSHM
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XShmAttach, xShmAttach,
|
||||
(::Display*, XShmSegmentInfo*),
|
||||
Bool)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XShmCreateImage, xShmCreateImage,
|
||||
(::Display*, Visual*, unsigned int, int, const char*, XShmSegmentInfo*, unsigned int, unsigned int),
|
||||
XImage*)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XShmDetach, xShmDetach,
|
||||
(::Display*, XShmSegmentInfo*),
|
||||
Bool)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XShmGetEventBase, xShmGetEventBase,
|
||||
(::Display*),
|
||||
Status)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XShmPutImage, xShmPutImage,
|
||||
(::Display*, ::Drawable, GC, XImage*, int, int, int, int, unsigned int, unsigned int, bool),
|
||||
Bool)
|
||||
|
||||
JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XShmQueryVersion, xShmQueryVersion,
|
||||
(::Display*, int*, int*, Bool*),
|
||||
Bool)
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_SINGLETON (X11Symbols, false)
|
||||
|
||||
private:
|
||||
X11Symbols() = default;
|
||||
|
||||
~X11Symbols()
|
||||
{
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
DynamicLibrary xLib { "libX11.so.6" }, xextLib { "libXext.so.6" };
|
||||
|
||||
#if JUCE_USE_XCURSOR
|
||||
DynamicLibrary xcursorLib { "libXcursor.so.1" };
|
||||
#endif
|
||||
#if JUCE_USE_XINERAMA
|
||||
DynamicLibrary xineramaLib { "libXinerama.so.1" };
|
||||
#endif
|
||||
#if JUCE_USE_XRENDER
|
||||
DynamicLibrary xrenderLib { "libXrender.so.1" };
|
||||
#endif
|
||||
#if JUCE_USE_XRANDR
|
||||
DynamicLibrary xrandrLib { "libXrandr.so.2" };
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (X11Symbols)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+4021
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
namespace XWindowSystemUtilities
|
||||
{
|
||||
//==============================================================================
|
||||
/** A handy struct that uses XLockDisplay and XUnlockDisplay to lock the X server
|
||||
using RAII.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
struct ScopedXLock
|
||||
{
|
||||
ScopedXLock();
|
||||
~ScopedXLock();
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Gets a specified window property and stores its associated data, freeing it
|
||||
on deletion.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
struct GetXProperty
|
||||
{
|
||||
GetXProperty (::Display* display, ::Window windowH, Atom property,
|
||||
long offset, long length, bool shouldDelete, Atom requestedType);
|
||||
~GetXProperty();
|
||||
|
||||
bool success = false;
|
||||
unsigned char* data = nullptr;
|
||||
unsigned long numItems = 0, bytesLeft = 0;
|
||||
Atom actualType;
|
||||
int actualFormat = -1;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises and stores some atoms for the display.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
struct Atoms
|
||||
{
|
||||
enum ProtocolItems
|
||||
{
|
||||
TAKE_FOCUS = 0,
|
||||
DELETE_WINDOW = 1,
|
||||
PING = 2
|
||||
};
|
||||
|
||||
Atoms() = default;
|
||||
explicit Atoms (::Display*);
|
||||
|
||||
static Atom getIfExists (::Display*, const char* name);
|
||||
static Atom getCreating (::Display*, const char* name);
|
||||
|
||||
static String getName (::Display*, Atom);
|
||||
static bool isMimeTypeFile (::Display*, Atom);
|
||||
|
||||
static constexpr unsigned long DndVersion = 3;
|
||||
|
||||
Atom protocols, protocolList[3], changeState, state, userTime, activeWin, pid, windowType, windowState, windowStateHidden,
|
||||
XdndAware, XdndEnter, XdndLeave, XdndPosition, XdndStatus, XdndDrop, XdndFinished, XdndSelection,
|
||||
XdndTypeList, XdndActionList, XdndActionDescription, XdndActionCopy, XdndActionPrivate,
|
||||
XembedMsgType, XembedInfo, allowedActions[5], allowedMimeTypes[4], utf8String, clipboard, targets;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a setting according to the XSETTINGS specification.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
struct XSetting
|
||||
{
|
||||
enum class Type
|
||||
{
|
||||
integer,
|
||||
string,
|
||||
colour,
|
||||
invalid
|
||||
};
|
||||
|
||||
XSetting() = default;
|
||||
|
||||
XSetting (const String& n, int v) : name (n), type (Type::integer), integerValue (v) {}
|
||||
XSetting (const String& n, const String& v) : name (n), type (Type::string), stringValue (v) {}
|
||||
XSetting (const String& n, const Colour& v) : name (n), type (Type::colour), colourValue (v) {}
|
||||
|
||||
bool isValid() const noexcept { return type != Type::invalid; }
|
||||
|
||||
String name;
|
||||
Type type = Type::invalid;
|
||||
|
||||
int integerValue = -1;
|
||||
String stringValue;
|
||||
Colour colourValue;
|
||||
};
|
||||
|
||||
/** Parses and stores the X11 settings for a display according to the XSETTINGS
|
||||
specification.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class XSettings
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<XSettings> createXSettings (::Display*);
|
||||
|
||||
//==============================================================================
|
||||
void update();
|
||||
::Window getSettingsWindow() const noexcept { return settingsWindow; }
|
||||
|
||||
XSetting getSetting (const String& settingName) const;
|
||||
|
||||
//==============================================================================
|
||||
struct Listener
|
||||
{
|
||||
virtual ~Listener() = default;
|
||||
virtual void settingChanged (const XSetting& settingThatHasChanged) = 0;
|
||||
};
|
||||
|
||||
void addListener (Listener* listenerToAdd) { listeners.add (listenerToAdd); }
|
||||
void removeListener (Listener* listenerToRemove) { listeners.remove (listenerToRemove); }
|
||||
|
||||
private:
|
||||
::Display* display = nullptr;
|
||||
::Window settingsWindow = None;
|
||||
Atom settingsAtom;
|
||||
|
||||
int lastUpdateSerial = -1;
|
||||
|
||||
std::unordered_map<String, XSetting> settings;
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
XSettings (::Display*, Atom, ::Window);
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XSettings)
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class LinuxComponentPeer;
|
||||
|
||||
class XWindowSystem : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
::Window createWindow (::Window parentWindow, LinuxComponentPeer*) const;
|
||||
void destroyWindow (::Window);
|
||||
|
||||
void setTitle (::Window, const String&) const;
|
||||
void setIcon (::Window , const Image&) const;
|
||||
void setVisible (::Window, bool shouldBeVisible) const;
|
||||
void setBounds (::Window, Rectangle<int>, bool fullScreen) const;
|
||||
void updateConstraints (::Window) const;
|
||||
|
||||
ComponentPeer::OptionalBorderSize getBorderSize (::Window) const;
|
||||
Rectangle<int> getWindowBounds (::Window, ::Window parentWindow);
|
||||
Point<int> getPhysicalParentScreenPosition() const;
|
||||
|
||||
bool contains (::Window, Point<int> localPos) const;
|
||||
|
||||
void setMinimised (::Window, bool shouldBeMinimised) const;
|
||||
bool isMinimised (::Window) const;
|
||||
|
||||
void setMaximised (::Window, bool shouldBeMinimised) const;
|
||||
|
||||
void toFront (::Window, bool makeActive) const;
|
||||
void toBehind (::Window, ::Window otherWindow) const;
|
||||
|
||||
bool isFocused (::Window) const;
|
||||
bool grabFocus (::Window) const;
|
||||
|
||||
bool canUseSemiTransparentWindows() const;
|
||||
bool canUseARGBImages() const;
|
||||
bool isDarkModeActive() const;
|
||||
|
||||
int getNumPaintsPendingForWindow (::Window);
|
||||
void processPendingPaintsForWindow (::Window);
|
||||
void addPendingPaintForWindow (::Window);
|
||||
void removePendingPaintForWindow (::Window);
|
||||
|
||||
Image createImage (bool isSemiTransparentWindow, int width, int height, bool argb) const;
|
||||
void blitToWindow (::Window, Image, Rectangle<int> destinationRect, Rectangle<int> totalRect) const;
|
||||
|
||||
void setScreenSaverEnabled (bool enabled) const;
|
||||
|
||||
Point<float> getCurrentMousePosition() const;
|
||||
void setMousePosition (Point<float> pos) const;
|
||||
|
||||
Cursor createCustomMouseCursorInfo (const Image&, Point<int> hotspot) const;
|
||||
void deleteMouseCursor (Cursor cursorHandle) const;
|
||||
Cursor createStandardMouseCursor (MouseCursor::StandardCursorType) const;
|
||||
void showCursor (::Window, Cursor cursorHandle) const;
|
||||
|
||||
bool isKeyCurrentlyDown (int keyCode) const;
|
||||
ModifierKeys getNativeRealtimeModifiers() const;
|
||||
|
||||
Array<Displays::Display> findDisplays (float masterScale) const;
|
||||
|
||||
::Window createKeyProxy (::Window);
|
||||
void deleteKeyProxy (::Window) const;
|
||||
|
||||
bool externalDragFileInit (LinuxComponentPeer*, const StringArray& files, bool canMove, std::function<void()>&& callback) const;
|
||||
bool externalDragTextInit (LinuxComponentPeer*, const String& text, std::function<void()>&& callback) const;
|
||||
|
||||
void copyTextToClipboard (const String&);
|
||||
String getTextFromClipboard() const;
|
||||
String getLocalClipboardContent() const noexcept { return localClipboardContent; }
|
||||
|
||||
::Display* getDisplay() const noexcept { return display; }
|
||||
const XWindowSystemUtilities::Atoms& getAtoms() const noexcept { return atoms; }
|
||||
XWindowSystemUtilities::XSettings* getXSettings() const noexcept { return xSettings.get(); }
|
||||
|
||||
bool isX11Available() const noexcept { return xIsAvailable; }
|
||||
|
||||
void startHostManagedResize (::Window window,
|
||||
ResizableBorderComponent::Zone zone);
|
||||
|
||||
static String getWindowScalingFactorSettingName() { return "Gdk/WindowScalingFactor"; }
|
||||
static String getThemeNameSettingName() { return "Net/ThemeName"; }
|
||||
|
||||
//==============================================================================
|
||||
void handleWindowMessage (LinuxComponentPeer*, XEvent&) const;
|
||||
bool isParentWindowOf (::Window, ::Window possibleChild) const;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_SINGLETON (XWindowSystem, false)
|
||||
|
||||
private:
|
||||
XWindowSystem();
|
||||
~XWindowSystem();
|
||||
|
||||
//==============================================================================
|
||||
struct VisualAndDepth
|
||||
{
|
||||
Visual* visual;
|
||||
int depth;
|
||||
};
|
||||
|
||||
struct DisplayVisuals
|
||||
{
|
||||
explicit DisplayVisuals (::Display*);
|
||||
|
||||
VisualAndDepth getBestVisualForWindow (bool) const;
|
||||
bool isValid() const noexcept;
|
||||
|
||||
Visual* visual16Bit = nullptr;
|
||||
Visual* visual24Bit = nullptr;
|
||||
Visual* visual32Bit = nullptr;
|
||||
};
|
||||
|
||||
bool initialiseXDisplay();
|
||||
void destroyXDisplay();
|
||||
|
||||
//==============================================================================
|
||||
::Window getFocusWindow (::Window) const;
|
||||
|
||||
bool isFrontWindow (::Window) const;
|
||||
|
||||
//==============================================================================
|
||||
void xchangeProperty (::Window, Atom, Atom, int, const void*, int) const;
|
||||
|
||||
void removeWindowDecorations (::Window) const;
|
||||
void addWindowButtons (::Window, int) const;
|
||||
void setWindowType (::Window, int) const;
|
||||
|
||||
void initialisePointerMap();
|
||||
void deleteIconPixmaps (::Window) const;
|
||||
void updateModifierMappings() const;
|
||||
|
||||
long getUserTime (::Window) const;
|
||||
bool isHidden (Window) const;
|
||||
bool isIconic (Window) const;
|
||||
|
||||
void initialiseXSettings();
|
||||
|
||||
//==============================================================================
|
||||
void handleKeyPressEvent (LinuxComponentPeer*, XKeyEvent&) const;
|
||||
void handleKeyReleaseEvent (LinuxComponentPeer*, const XKeyEvent&) const;
|
||||
void handleWheelEvent (LinuxComponentPeer*, const XButtonPressedEvent&, float) const;
|
||||
void handleButtonPressEvent (LinuxComponentPeer*, const XButtonPressedEvent&, int) const;
|
||||
void handleButtonPressEvent (LinuxComponentPeer*, const XButtonPressedEvent&) const;
|
||||
void handleButtonReleaseEvent (LinuxComponentPeer*, const XButtonReleasedEvent&) const;
|
||||
void handleMotionNotifyEvent (LinuxComponentPeer*, const XPointerMovedEvent&) const;
|
||||
void handleEnterNotifyEvent (LinuxComponentPeer*, const XEnterWindowEvent&) const;
|
||||
void handleLeaveNotifyEvent (LinuxComponentPeer*, const XLeaveWindowEvent&) const;
|
||||
void handleFocusInEvent (LinuxComponentPeer*) const;
|
||||
void handleFocusOutEvent (LinuxComponentPeer*) const;
|
||||
void handleExposeEvent (LinuxComponentPeer*, XExposeEvent&) const;
|
||||
void handleConfigureNotifyEvent (LinuxComponentPeer*, XConfigureEvent&) const;
|
||||
void handleGravityNotify (LinuxComponentPeer*) const;
|
||||
void propertyNotifyEvent (LinuxComponentPeer*, const XPropertyEvent&) const;
|
||||
void handleMappingNotify (XMappingEvent&) const;
|
||||
void handleClientMessageEvent (LinuxComponentPeer*, XClientMessageEvent&, XEvent&) const;
|
||||
void handleXEmbedMessage (LinuxComponentPeer*, XClientMessageEvent&) const;
|
||||
|
||||
void dismissBlockingModals (LinuxComponentPeer*) const;
|
||||
void dismissBlockingModals (LinuxComponentPeer*, const XConfigureEvent&) const;
|
||||
void updateConstraints (::Window, ComponentPeer&) const;
|
||||
|
||||
::Window findTopLevelWindowOf (::Window) const;
|
||||
|
||||
static void windowMessageReceive (XEvent&);
|
||||
|
||||
//==============================================================================
|
||||
bool xIsAvailable = false;
|
||||
|
||||
XWindowSystemUtilities::Atoms atoms;
|
||||
::Display* display = nullptr;
|
||||
std::unique_ptr<DisplayVisuals> displayVisuals;
|
||||
std::unique_ptr<XWindowSystemUtilities::XSettings> xSettings;
|
||||
|
||||
#if JUCE_USE_XSHM
|
||||
std::map<::Window, int> shmPaintsPendingMap;
|
||||
#endif
|
||||
|
||||
int shmCompletionEvent = 0;
|
||||
int pointerMap[5] = {};
|
||||
String localClipboardContent;
|
||||
|
||||
Point<int> parentScreenPosition;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XWindowSystem)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Reference in New Issue
Block a user