git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
111
deps/juce/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h
vendored
Normal file
111
deps/juce/modules/juce_gui_basics/native/accessibility/juce_AccessibilityTextHelpers.h
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 AccessibilityTextHelpers
|
||||
{
|
||||
enum class BoundaryType
|
||||
{
|
||||
character,
|
||||
word,
|
||||
line,
|
||||
document
|
||||
};
|
||||
|
||||
enum class Direction
|
||||
{
|
||||
forwards,
|
||||
backwards
|
||||
};
|
||||
|
||||
static int findTextBoundary (const AccessibilityTextInterface& textInterface,
|
||||
int currentPosition,
|
||||
BoundaryType boundary,
|
||||
Direction direction)
|
||||
{
|
||||
const auto numCharacters = textInterface.getTotalNumCharacters();
|
||||
const auto isForwards = (direction == Direction::forwards);
|
||||
|
||||
auto offsetWithDirecton = [isForwards] (int input) { return isForwards ? input : -input; };
|
||||
|
||||
switch (boundary)
|
||||
{
|
||||
case BoundaryType::character:
|
||||
return jlimit (0, numCharacters, currentPosition + offsetWithDirecton (1));
|
||||
|
||||
case BoundaryType::word:
|
||||
case BoundaryType::line:
|
||||
{
|
||||
const auto text = [&]() -> String
|
||||
{
|
||||
if (isForwards)
|
||||
return textInterface.getText ({ currentPosition, textInterface.getTotalNumCharacters() });
|
||||
|
||||
const auto str = textInterface.getText ({ 0, currentPosition });
|
||||
|
||||
auto start = str.getCharPointer();
|
||||
auto end = start.findTerminatingNull();
|
||||
const auto size = getAddressDifference (end.getAddress(), start.getAddress());
|
||||
|
||||
String reversed;
|
||||
|
||||
if (size > 0)
|
||||
{
|
||||
reversed.preallocateBytes ((size_t) size);
|
||||
|
||||
auto destPtr = reversed.getCharPointer();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
destPtr.write (*--end);
|
||||
|
||||
if (end == start)
|
||||
break;
|
||||
}
|
||||
|
||||
destPtr.writeNull();
|
||||
}
|
||||
|
||||
return reversed;
|
||||
}();
|
||||
|
||||
auto tokens = (boundary == BoundaryType::line ? StringArray::fromLines (text)
|
||||
: StringArray::fromTokens (text, false));
|
||||
|
||||
return currentPosition + offsetWithDirecton (tokens[0].length());
|
||||
}
|
||||
|
||||
case BoundaryType::document:
|
||||
return isForwards ? numCharacters : 0;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
918
deps/juce/modules/juce_gui_basics/native/accessibility/juce_android_Accessibility.cpp
vendored
Normal file
918
deps/juce/modules/juce_gui_basics/native/accessibility/juce_android_Accessibility.cpp
vendored
Normal file
@ -0,0 +1,918 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (constructor, "<init>", "()V") \
|
||||
METHOD (setSource, "setSource", "(Landroid/view/View;I)V") \
|
||||
METHOD (addChild, "addChild", "(Landroid/view/View;I)V") \
|
||||
METHOD (setParent, "setParent", "(Landroid/view/View;)V") \
|
||||
METHOD (setVirtualParent, "setParent", "(Landroid/view/View;I)V") \
|
||||
METHOD (setBoundsInScreen, "setBoundsInScreen", "(Landroid/graphics/Rect;)V") \
|
||||
METHOD (setBoundsInParent, "setBoundsInParent", "(Landroid/graphics/Rect;)V") \
|
||||
METHOD (setPackageName, "setPackageName", "(Ljava/lang/CharSequence;)V") \
|
||||
METHOD (setClassName, "setClassName", "(Ljava/lang/CharSequence;)V") \
|
||||
METHOD (setContentDescription, "setContentDescription", "(Ljava/lang/CharSequence;)V") \
|
||||
METHOD (setCheckable, "setCheckable", "(Z)V") \
|
||||
METHOD (setChecked, "setChecked", "(Z)V") \
|
||||
METHOD (setClickable, "setClickable", "(Z)V") \
|
||||
METHOD (setEnabled, "setEnabled", "(Z)V") \
|
||||
METHOD (setFocusable, "setFocusable", "(Z)V") \
|
||||
METHOD (setFocused, "setFocused", "(Z)V") \
|
||||
METHOD (setPassword, "setPassword", "(Z)V") \
|
||||
METHOD (setSelected, "setSelected", "(Z)V") \
|
||||
METHOD (setVisibleToUser, "setVisibleToUser", "(Z)V") \
|
||||
METHOD (setAccessibilityFocused, "setAccessibilityFocused", "(Z)V") \
|
||||
METHOD (setText, "setText", "(Ljava/lang/CharSequence;)V") \
|
||||
METHOD (setMovementGranularities, "setMovementGranularities", "(I)V") \
|
||||
METHOD (addAction, "addAction", "(I)V") \
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidAccessibilityNodeInfo, "android/view/accessibility/AccessibilityNodeInfo")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
STATICMETHOD (obtain, "obtain", "(I)Landroid/view/accessibility/AccessibilityEvent;") \
|
||||
METHOD (setPackageName, "setPackageName", "(Ljava/lang/CharSequence;)V") \
|
||||
METHOD (setSource, "setSource", "(Landroid/view/View;I)V") \
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidAccessibilityEvent, "android/view/accessibility/AccessibilityEvent")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (isEnabled, "isEnabled", "()Z") \
|
||||
|
||||
DECLARE_JNI_CLASS (AndroidAccessibilityManager, "android/view/accessibility/AccessibilityManager")
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int HOST_VIEW_ID = -1;
|
||||
|
||||
constexpr int TYPE_VIEW_CLICKED = 0x00000001,
|
||||
TYPE_VIEW_SELECTED = 0x00000004,
|
||||
TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000,
|
||||
TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000,
|
||||
TYPE_WINDOW_CONTENT_CHANGED = 0x00000800,
|
||||
TYPE_VIEW_TEXT_SELECTION_CHANGED = 0x00002000,
|
||||
TYPE_VIEW_TEXT_CHANGED = 0x00000010;
|
||||
|
||||
constexpr int CONTENT_CHANGE_TYPE_SUBTREE = 0x00000001,
|
||||
CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
|
||||
|
||||
constexpr int ACTION_ACCESSIBILITY_FOCUS = 0x00000040,
|
||||
ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000080,
|
||||
ACTION_CLEAR_FOCUS = 0x00000002,
|
||||
ACTION_CLEAR_SELECTION = 0x00000008,
|
||||
ACTION_CLICK = 0x00000010,
|
||||
ACTION_COLLAPSE = 0x00080000,
|
||||
ACTION_EXPAND = 0x00040000,
|
||||
ACTION_FOCUS = 0x00000001,
|
||||
ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100,
|
||||
ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200,
|
||||
ACTION_SCROLL_BACKWARD = 0x00002000,
|
||||
ACTION_SCROLL_FORWARD = 0x00001000,
|
||||
ACTION_SELECT = 0x00000004,
|
||||
ACTION_SET_SELECTION = 0x00020000,
|
||||
ACTION_SET_TEXT = 0x00200000;
|
||||
|
||||
constexpr int MOVEMENT_GRANULARITY_CHARACTER = 0x00000001,
|
||||
MOVEMENT_GRANULARITY_LINE = 0x00000004,
|
||||
MOVEMENT_GRANULARITY_PAGE = 0x00000010,
|
||||
MOVEMENT_GRANULARITY_PARAGRAPH = 0x00000008,
|
||||
MOVEMENT_GRANULARITY_WORD = 0x00000002,
|
||||
ALL_GRANULARITIES = MOVEMENT_GRANULARITY_CHARACTER
|
||||
| MOVEMENT_GRANULARITY_LINE
|
||||
| MOVEMENT_GRANULARITY_PAGE
|
||||
| MOVEMENT_GRANULARITY_PARAGRAPH
|
||||
| MOVEMENT_GRANULARITY_WORD;
|
||||
|
||||
constexpr int ACCESSIBILITY_LIVE_REGION_POLITE = 0x00000001;
|
||||
}
|
||||
|
||||
static jmethodID nodeInfoSetEditable = nullptr;
|
||||
static jmethodID nodeInfoSetTextSelection = nullptr;
|
||||
static jmethodID nodeInfoSetLiveRegion = nullptr;
|
||||
static jmethodID accessibilityEventSetContentChangeTypes = nullptr;
|
||||
|
||||
static void loadSDKDependentMethods()
|
||||
{
|
||||
static bool hasChecked = false;
|
||||
|
||||
if (! hasChecked)
|
||||
{
|
||||
hasChecked = true;
|
||||
|
||||
auto* env = getEnv();
|
||||
const auto sdkVersion = getAndroidSDKVersion();
|
||||
|
||||
if (sdkVersion >= 18)
|
||||
{
|
||||
nodeInfoSetEditable = env->GetMethodID (AndroidAccessibilityNodeInfo, "setEditable", "(Z)V");
|
||||
nodeInfoSetTextSelection = env->GetMethodID (AndroidAccessibilityNodeInfo, "setTextSelection", "(II)V");
|
||||
}
|
||||
|
||||
if (sdkVersion >= 19)
|
||||
{
|
||||
nodeInfoSetLiveRegion = env->GetMethodID (AndroidAccessibilityNodeInfo, "setLiveRegion", "(I)V");
|
||||
accessibilityEventSetContentChangeTypes = env->GetMethodID (AndroidAccessibilityEvent, "setContentChangeTypes", "(I)V");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr auto getClassName (AccessibilityRole role)
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case AccessibilityRole::editableText: return "android.widget.EditText";
|
||||
case AccessibilityRole::toggleButton: return "android.widget.CheckBox";
|
||||
case AccessibilityRole::radioButton: return "android.widget.RadioButton";
|
||||
case AccessibilityRole::image: return "android.widget.ImageView";
|
||||
case AccessibilityRole::popupMenu: return "android.widget.PopupMenu";
|
||||
case AccessibilityRole::comboBox: return "android.widget.Spinner";
|
||||
case AccessibilityRole::tree: return "android.widget.ExpandableListView";
|
||||
case AccessibilityRole::list: return "android.widget.ListView";
|
||||
case AccessibilityRole::table: return "android.widget.TableLayout";
|
||||
case AccessibilityRole::progressBar: return "android.widget.ProgressBar";
|
||||
|
||||
case AccessibilityRole::scrollBar:
|
||||
case AccessibilityRole::slider: return "android.widget.SeekBar";
|
||||
|
||||
case AccessibilityRole::hyperlink:
|
||||
case AccessibilityRole::button: return "android.widget.Button";
|
||||
|
||||
case AccessibilityRole::label:
|
||||
case AccessibilityRole::staticText: return "android.widget.TextView";
|
||||
|
||||
case AccessibilityRole::tooltip:
|
||||
case AccessibilityRole::splashScreen:
|
||||
case AccessibilityRole::dialogWindow: return "android.widget.PopupWindow";
|
||||
|
||||
case AccessibilityRole::column:
|
||||
case AccessibilityRole::row:
|
||||
case AccessibilityRole::cell:
|
||||
case AccessibilityRole::menuItem:
|
||||
case AccessibilityRole::menuBar:
|
||||
case AccessibilityRole::listItem:
|
||||
case AccessibilityRole::treeItem:
|
||||
case AccessibilityRole::window:
|
||||
case AccessibilityRole::tableHeader:
|
||||
case AccessibilityRole::unspecified:
|
||||
case AccessibilityRole::group:
|
||||
case AccessibilityRole::ignored: break;
|
||||
}
|
||||
|
||||
return "android.view.View";
|
||||
}
|
||||
|
||||
static jobject getSourceView (const AccessibilityHandler& handler)
|
||||
{
|
||||
if (auto* peer = handler.getComponent().getPeer())
|
||||
return (jobject) peer->getNativeHandle();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes);
|
||||
|
||||
//==============================================================================
|
||||
class AccessibilityNativeHandle
|
||||
{
|
||||
public:
|
||||
static AccessibilityHandler* getAccessibilityHandlerForVirtualViewId (int virtualViewId)
|
||||
{
|
||||
auto iter = virtualViewIdMap.find (virtualViewId);
|
||||
|
||||
if (iter != virtualViewIdMap.end())
|
||||
return iter->second;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
explicit AccessibilityNativeHandle (AccessibilityHandler& h)
|
||||
: accessibilityHandler (h),
|
||||
virtualViewId (getVirtualViewIdForHandler (accessibilityHandler))
|
||||
{
|
||||
loadSDKDependentMethods();
|
||||
|
||||
if (virtualViewId != HOST_VIEW_ID)
|
||||
virtualViewIdMap[virtualViewId] = &accessibilityHandler;
|
||||
}
|
||||
|
||||
~AccessibilityNativeHandle()
|
||||
{
|
||||
if (virtualViewId != HOST_VIEW_ID)
|
||||
virtualViewIdMap.erase (virtualViewId);
|
||||
}
|
||||
|
||||
int getVirtualViewId() const noexcept { return virtualViewId; }
|
||||
|
||||
void populateNodeInfo (jobject info)
|
||||
{
|
||||
const ScopedValueSetter<bool> svs (inPopulateNodeInfo, true);
|
||||
|
||||
const auto sourceView = getSourceView (accessibilityHandler);
|
||||
|
||||
if (sourceView == nullptr)
|
||||
return;
|
||||
|
||||
auto* env = getEnv();
|
||||
auto appContext = getAppContext();
|
||||
|
||||
if (appContext.get() == nullptr)
|
||||
return;
|
||||
|
||||
{
|
||||
for (auto* child : accessibilityHandler.getChildren())
|
||||
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.addChild,
|
||||
sourceView, child->getNativeImplementation()->getVirtualViewId());
|
||||
|
||||
if (auto* parent = accessibilityHandler.getParent())
|
||||
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setVirtualParent,
|
||||
sourceView, parent->getNativeImplementation()->getVirtualViewId());
|
||||
else
|
||||
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setParent, sourceView);
|
||||
}
|
||||
|
||||
{
|
||||
const auto scale = Desktop::getInstance().getDisplays().getPrimaryDisplay()->scale;
|
||||
|
||||
const auto screenBounds = accessibilityHandler.getComponent().getScreenBounds() * scale;
|
||||
|
||||
LocalRef<jobject> rect (env->NewObject (AndroidRect, AndroidRect.constructor,
|
||||
screenBounds.getX(), screenBounds.getY(),
|
||||
screenBounds.getRight(), screenBounds.getBottom()));
|
||||
|
||||
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setBoundsInScreen, rect.get());
|
||||
|
||||
const auto boundsInParent = accessibilityHandler.getComponent().getBoundsInParent() * scale;
|
||||
|
||||
rect = LocalRef<jobject> (env->NewObject (AndroidRect, AndroidRect.constructor,
|
||||
boundsInParent.getX(), boundsInParent.getY(),
|
||||
boundsInParent.getRight(), boundsInParent.getBottom()));
|
||||
|
||||
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setBoundsInParent, rect.get());
|
||||
}
|
||||
|
||||
const auto state = accessibilityHandler.getCurrentState();
|
||||
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setEnabled,
|
||||
! state.isIgnored());
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setVisibleToUser,
|
||||
true);
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setPackageName,
|
||||
env->CallObjectMethod (appContext.get(),
|
||||
AndroidContext.getPackageName));
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setSource,
|
||||
sourceView,
|
||||
virtualViewId);
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setClassName,
|
||||
javaString (getClassName (accessibilityHandler.getRole())).get());
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setContentDescription,
|
||||
getDescriptionString().get());
|
||||
|
||||
if (state.isFocusable())
|
||||
{
|
||||
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.setFocusable, true);
|
||||
|
||||
const auto& component = accessibilityHandler.getComponent();
|
||||
|
||||
if (component.getWantsKeyboardFocus())
|
||||
{
|
||||
const auto hasKeyboardFocus = component.hasKeyboardFocus (false);
|
||||
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setFocused,
|
||||
hasKeyboardFocus);
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.addAction,
|
||||
hasKeyboardFocus ? ACTION_CLEAR_FOCUS : ACTION_FOCUS);
|
||||
}
|
||||
|
||||
const auto isAccessibleFocused = accessibilityHandler.hasFocus (false);
|
||||
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setAccessibilityFocused,
|
||||
isAccessibleFocused);
|
||||
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.addAction,
|
||||
isAccessibleFocused ? ACTION_CLEAR_ACCESSIBILITY_FOCUS
|
||||
: ACTION_ACCESSIBILITY_FOCUS);
|
||||
}
|
||||
|
||||
if (state.isCheckable())
|
||||
{
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setCheckable,
|
||||
true);
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setChecked,
|
||||
state.isChecked());
|
||||
}
|
||||
|
||||
if (state.isSelectable() || state.isMultiSelectable())
|
||||
{
|
||||
const auto isSelected = state.isSelected();
|
||||
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setSelected,
|
||||
isSelected);
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.addAction,
|
||||
isSelected ? ACTION_CLEAR_SELECTION : ACTION_SELECT);
|
||||
}
|
||||
|
||||
if (accessibilityHandler.getActions().contains (AccessibilityActionType::press))
|
||||
{
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setClickable,
|
||||
true);
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.addAction,
|
||||
ACTION_CLICK);
|
||||
}
|
||||
|
||||
if (accessibilityHandler.getActions().contains (AccessibilityActionType::showMenu)
|
||||
&& state.isExpandable())
|
||||
{
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.addAction,
|
||||
state.isExpanded() ? ACTION_COLLAPSE : ACTION_EXPAND);
|
||||
}
|
||||
|
||||
if (auto* textInterface = accessibilityHandler.getTextInterface())
|
||||
{
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setText,
|
||||
javaString (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() })).get());
|
||||
|
||||
const auto isReadOnly = textInterface->isReadOnly();
|
||||
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setPassword,
|
||||
textInterface->isDisplayingProtectedText());
|
||||
|
||||
if (nodeInfoSetEditable != nullptr)
|
||||
env->CallVoidMethod (info, nodeInfoSetEditable, ! isReadOnly);
|
||||
|
||||
const auto selection = textInterface->getSelection();
|
||||
|
||||
if (nodeInfoSetTextSelection != nullptr && ! selection.isEmpty())
|
||||
env->CallVoidMethod (info,
|
||||
nodeInfoSetTextSelection,
|
||||
selection.getStart(), selection.getEnd());
|
||||
|
||||
if (nodeInfoSetLiveRegion != nullptr && accessibilityHandler.hasFocus (false))
|
||||
env->CallVoidMethod (info,
|
||||
nodeInfoSetLiveRegion,
|
||||
ACCESSIBILITY_LIVE_REGION_POLITE);
|
||||
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.setMovementGranularities,
|
||||
ALL_GRANULARITIES);
|
||||
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.addAction,
|
||||
ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.addAction,
|
||||
ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.addAction,
|
||||
ACTION_SET_SELECTION);
|
||||
|
||||
if (! isReadOnly)
|
||||
env->CallVoidMethod (info, AndroidAccessibilityNodeInfo.addAction, ACTION_SET_TEXT);
|
||||
}
|
||||
|
||||
if (auto* valueInterface = accessibilityHandler.getValueInterface())
|
||||
{
|
||||
if (! valueInterface->isReadOnly())
|
||||
{
|
||||
const auto range = valueInterface->getRange();
|
||||
|
||||
if (range.isValid())
|
||||
{
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.addAction,
|
||||
ACTION_SCROLL_FORWARD);
|
||||
env->CallVoidMethod (info,
|
||||
AndroidAccessibilityNodeInfo.addAction,
|
||||
ACTION_SCROLL_BACKWARD);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool performAction (int action, jobject arguments)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case ACTION_ACCESSIBILITY_FOCUS:
|
||||
{
|
||||
const WeakReference<Component> safeComponent (&accessibilityHandler.getComponent());
|
||||
|
||||
accessibilityHandler.getActions().invoke (AccessibilityActionType::focus);
|
||||
|
||||
if (safeComponent != nullptr)
|
||||
accessibilityHandler.grabFocus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case ACTION_CLEAR_ACCESSIBILITY_FOCUS:
|
||||
{
|
||||
accessibilityHandler.giveAwayFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
case ACTION_FOCUS:
|
||||
case ACTION_CLEAR_FOCUS:
|
||||
{
|
||||
auto& component = accessibilityHandler.getComponent();
|
||||
|
||||
if (component.getWantsKeyboardFocus())
|
||||
{
|
||||
const auto hasFocus = component.hasKeyboardFocus (false);
|
||||
|
||||
if (hasFocus && action == ACTION_CLEAR_FOCUS)
|
||||
component.giveAwayKeyboardFocus();
|
||||
else if (! hasFocus && action == ACTION_FOCUS)
|
||||
component.grabKeyboardFocus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ACTION_CLICK:
|
||||
{
|
||||
if (accessibilityHandler.getActions().invoke (AccessibilityActionType::press))
|
||||
{
|
||||
sendAccessibilityEventImpl (accessibilityHandler, TYPE_VIEW_CLICKED, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ACTION_SELECT:
|
||||
case ACTION_CLEAR_SELECTION:
|
||||
{
|
||||
const auto state = accessibilityHandler.getCurrentState();
|
||||
|
||||
if (state.isSelectable() || state.isMultiSelectable())
|
||||
{
|
||||
const auto isSelected = state.isSelected();
|
||||
|
||||
if ((isSelected && action == ACTION_CLEAR_SELECTION)
|
||||
|| (! isSelected && action == ACTION_SELECT))
|
||||
{
|
||||
return accessibilityHandler.getActions().invoke (AccessibilityActionType::toggle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ACTION_EXPAND:
|
||||
case ACTION_COLLAPSE:
|
||||
{
|
||||
const auto state = accessibilityHandler.getCurrentState();
|
||||
|
||||
if (state.isExpandable())
|
||||
{
|
||||
const auto isExpanded = state.isExpanded();
|
||||
|
||||
if ((isExpanded && action == ACTION_COLLAPSE)
|
||||
|| (! isExpanded && action == ACTION_EXPAND))
|
||||
{
|
||||
return accessibilityHandler.getActions().invoke (AccessibilityActionType::showMenu);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ACTION_NEXT_AT_MOVEMENT_GRANULARITY: return moveCursor (arguments, true);
|
||||
case ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: return moveCursor (arguments, false);
|
||||
|
||||
case ACTION_SET_SELECTION:
|
||||
{
|
||||
if (auto* textInterface = accessibilityHandler.getTextInterface())
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
const auto selection = [&]() -> Range<int>
|
||||
{
|
||||
const auto selectionStartKey = javaString ("ACTION_ARGUMENT_SELECTION_START_INT");
|
||||
const auto selectionEndKey = javaString ("ACTION_ARGUMENT_SELECTION_END_INT");
|
||||
|
||||
const auto hasKey = [&env, &arguments] (const auto& key)
|
||||
{
|
||||
return env->CallBooleanMethod (arguments, AndroidBundle.containsKey, key.get());
|
||||
};
|
||||
|
||||
if (hasKey (selectionStartKey) && hasKey (selectionEndKey))
|
||||
{
|
||||
const auto getKey = [&env, &arguments] (const auto& key)
|
||||
{
|
||||
return env->CallIntMethod (arguments, AndroidBundle.getInt, key.get());
|
||||
};
|
||||
|
||||
return { getKey (selectionStartKey), getKey (selectionEndKey) };
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
textInterface->setSelection (selection);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ACTION_SET_TEXT:
|
||||
{
|
||||
if (auto* textInterface = accessibilityHandler.getTextInterface())
|
||||
{
|
||||
if (! textInterface->isReadOnly())
|
||||
{
|
||||
const auto charSequenceKey = javaString ("ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE");
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
const auto text = [&]() -> String
|
||||
{
|
||||
if (env->CallBooleanMethod (arguments, AndroidBundle.containsKey, charSequenceKey.get()))
|
||||
{
|
||||
LocalRef<jobject> charSequence (env->CallObjectMethod (arguments,
|
||||
AndroidBundle.getCharSequence,
|
||||
charSequenceKey.get()));
|
||||
LocalRef<jstring> textStringRef ((jstring) env->CallObjectMethod (charSequence,
|
||||
JavaCharSequence.toString));
|
||||
|
||||
return juceString (textStringRef.get());
|
||||
}
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
textInterface->setText (text);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ACTION_SCROLL_BACKWARD:
|
||||
case ACTION_SCROLL_FORWARD:
|
||||
{
|
||||
if (auto* valueInterface = accessibilityHandler.getValueInterface())
|
||||
{
|
||||
if (! valueInterface->isReadOnly())
|
||||
{
|
||||
const auto range = valueInterface->getRange();
|
||||
|
||||
if (range.isValid())
|
||||
{
|
||||
const auto interval = action == ACTION_SCROLL_BACKWARD ? -range.getInterval()
|
||||
: range.getInterval();
|
||||
valueInterface->setValue (jlimit (range.getMinimumValue(),
|
||||
range.getMaximumValue(),
|
||||
valueInterface->getCurrentValue() + interval));
|
||||
|
||||
// required for Android to announce the new value
|
||||
sendAccessibilityEventImpl (accessibilityHandler, TYPE_VIEW_SELECTED, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isInPopulateNodeInfo() const noexcept { return inPopulateNodeInfo; }
|
||||
|
||||
private:
|
||||
static std::unordered_map<int, AccessibilityHandler*> virtualViewIdMap;
|
||||
|
||||
static int getVirtualViewIdForHandler (const AccessibilityHandler& handler)
|
||||
{
|
||||
static int counter = 0;
|
||||
|
||||
if (handler.getComponent().isOnDesktop())
|
||||
return HOST_VIEW_ID;
|
||||
|
||||
return counter++;
|
||||
}
|
||||
|
||||
LocalRef<jstring> getDescriptionString() const
|
||||
{
|
||||
const auto valueString = [this]() -> String
|
||||
{
|
||||
if (auto* textInterface = accessibilityHandler.getTextInterface())
|
||||
return textInterface->getText ({ 0, textInterface->getTotalNumCharacters() });
|
||||
|
||||
if (auto* valueInterface = accessibilityHandler.getValueInterface())
|
||||
return valueInterface->getCurrentValueAsString();
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
StringArray strings (accessibilityHandler.getTitle(),
|
||||
valueString,
|
||||
accessibilityHandler.getDescription(),
|
||||
accessibilityHandler.getHelp());
|
||||
|
||||
strings.removeEmptyStrings();
|
||||
|
||||
return javaString (strings.joinIntoString (","));
|
||||
}
|
||||
|
||||
bool moveCursor (jobject arguments, bool forwards)
|
||||
{
|
||||
if (auto* textInterface = accessibilityHandler.getTextInterface())
|
||||
{
|
||||
const auto granularityKey = javaString ("ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT");
|
||||
const auto extendSelectionKey = javaString ("ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN");
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
const auto boundaryType = [&]
|
||||
{
|
||||
const auto granularity = env->CallIntMethod (arguments,
|
||||
AndroidBundle.getInt,
|
||||
granularityKey.get());
|
||||
|
||||
using BoundaryType = AccessibilityTextHelpers::BoundaryType;
|
||||
|
||||
switch (granularity)
|
||||
{
|
||||
case MOVEMENT_GRANULARITY_CHARACTER: return BoundaryType::character;
|
||||
case MOVEMENT_GRANULARITY_WORD: return BoundaryType::word;
|
||||
case MOVEMENT_GRANULARITY_LINE: return BoundaryType::line;
|
||||
case MOVEMENT_GRANULARITY_PARAGRAPH:
|
||||
case MOVEMENT_GRANULARITY_PAGE: return BoundaryType::document;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return BoundaryType::character;
|
||||
}();
|
||||
|
||||
using Direction = AccessibilityTextHelpers::Direction;
|
||||
|
||||
const auto cursorPos = AccessibilityTextHelpers::findTextBoundary (*textInterface,
|
||||
textInterface->getTextInsertionOffset(),
|
||||
boundaryType,
|
||||
forwards ? Direction::forwards
|
||||
: Direction::backwards);
|
||||
|
||||
const auto newSelection = [&]() -> Range<int>
|
||||
{
|
||||
const auto currentSelection = textInterface->getSelection();
|
||||
const auto extendSelection = env->CallBooleanMethod (arguments,
|
||||
AndroidBundle.getBoolean,
|
||||
extendSelectionKey.get());
|
||||
|
||||
if (! extendSelection)
|
||||
return { cursorPos, cursorPos };
|
||||
|
||||
const auto start = currentSelection.getStart();
|
||||
const auto end = currentSelection.getEnd();
|
||||
|
||||
if (forwards)
|
||||
return { start, jmax (start, cursorPos) };
|
||||
|
||||
return { jmin (start, cursorPos), end };
|
||||
}();
|
||||
|
||||
textInterface->setSelection (newSelection);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AccessibilityHandler& accessibilityHandler;
|
||||
const int virtualViewId;
|
||||
bool inPopulateNodeInfo = false;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityNativeHandle)
|
||||
};
|
||||
|
||||
std::unordered_map<int, AccessibilityHandler*> AccessibilityNativeHandle::virtualViewIdMap;
|
||||
|
||||
class AccessibilityHandler::AccessibilityNativeImpl : public AccessibilityNativeHandle
|
||||
{
|
||||
public:
|
||||
using AccessibilityNativeHandle::AccessibilityNativeHandle;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const
|
||||
{
|
||||
return nativeImpl.get();
|
||||
}
|
||||
|
||||
static bool areAnyAccessibilityClientsActive()
|
||||
{
|
||||
auto* env = getEnv();
|
||||
auto appContext = getAppContext();
|
||||
|
||||
if (appContext.get() != nullptr)
|
||||
{
|
||||
LocalRef<jobject> accessibilityManager (env->CallObjectMethod (appContext.get(), AndroidContext.getSystemService,
|
||||
javaString ("accessibility").get()));
|
||||
|
||||
if (accessibilityManager != nullptr)
|
||||
return env->CallBooleanMethod (accessibilityManager.get(), AndroidAccessibilityManager.isEnabled);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sendAccessibilityEventImpl (const AccessibilityHandler& handler, int eventType, int contentChangeTypes)
|
||||
{
|
||||
if (! areAnyAccessibilityClientsActive())
|
||||
return;
|
||||
|
||||
if (const auto sourceView = getSourceView (handler))
|
||||
{
|
||||
const auto* nativeImpl = handler.getNativeImplementation();
|
||||
|
||||
if (nativeImpl == nullptr || nativeImpl->isInPopulateNodeInfo())
|
||||
return;
|
||||
|
||||
auto* env = getEnv();
|
||||
auto appContext = getAppContext();
|
||||
|
||||
if (appContext.get() == nullptr)
|
||||
return;
|
||||
|
||||
LocalRef<jobject> event (env->CallStaticObjectMethod (AndroidAccessibilityEvent,
|
||||
AndroidAccessibilityEvent.obtain,
|
||||
eventType));
|
||||
|
||||
env->CallVoidMethod (event,
|
||||
AndroidAccessibilityEvent.setPackageName,
|
||||
env->CallObjectMethod (appContext.get(),
|
||||
AndroidContext.getPackageName));
|
||||
|
||||
env->CallVoidMethod (event,
|
||||
AndroidAccessibilityEvent.setSource,
|
||||
sourceView,
|
||||
nativeImpl->getVirtualViewId());
|
||||
|
||||
if (contentChangeTypes != 0 && accessibilityEventSetContentChangeTypes != nullptr)
|
||||
env->CallVoidMethod (event,
|
||||
accessibilityEventSetContentChangeTypes,
|
||||
contentChangeTypes);
|
||||
|
||||
env->CallBooleanMethod (sourceView,
|
||||
AndroidViewGroup.requestSendAccessibilityEvent,
|
||||
sourceView,
|
||||
event.get());
|
||||
}
|
||||
}
|
||||
|
||||
void notifyAccessibilityEventInternal (const AccessibilityHandler& handler,
|
||||
InternalAccessibilityEvent eventType)
|
||||
{
|
||||
if (eventType == InternalAccessibilityEvent::elementCreated
|
||||
|| eventType == InternalAccessibilityEvent::elementDestroyed
|
||||
|| eventType == InternalAccessibilityEvent::elementMovedOrResized)
|
||||
{
|
||||
if (auto* parent = handler.getParent())
|
||||
sendAccessibilityEventImpl (*parent, TYPE_WINDOW_CONTENT_CHANGED, CONTENT_CHANGE_TYPE_SUBTREE);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto notification = [&handler, eventType]
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case InternalAccessibilityEvent::focusChanged:
|
||||
return handler.hasFocus (false) ? TYPE_VIEW_ACCESSIBILITY_FOCUSED
|
||||
: TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
|
||||
|
||||
case InternalAccessibilityEvent::elementCreated:
|
||||
case InternalAccessibilityEvent::elementDestroyed:
|
||||
case InternalAccessibilityEvent::elementMovedOrResized:
|
||||
case InternalAccessibilityEvent::windowOpened:
|
||||
case InternalAccessibilityEvent::windowClosed:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}();
|
||||
|
||||
if (notification != 0)
|
||||
sendAccessibilityEventImpl (handler, notification, 0);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent eventType) const
|
||||
{
|
||||
auto notification = [eventType]
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case AccessibilityEvent::textSelectionChanged: return TYPE_VIEW_TEXT_SELECTION_CHANGED;
|
||||
case AccessibilityEvent::textChanged: return TYPE_VIEW_TEXT_CHANGED;
|
||||
|
||||
case AccessibilityEvent::titleChanged:
|
||||
case AccessibilityEvent::structureChanged: return TYPE_WINDOW_CONTENT_CHANGED;
|
||||
|
||||
case AccessibilityEvent::rowSelectionChanged:
|
||||
case AccessibilityEvent::valueChanged: break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}();
|
||||
|
||||
if (notification == 0)
|
||||
return;
|
||||
|
||||
const auto contentChangeTypes = [eventType]
|
||||
{
|
||||
if (eventType == AccessibilityEvent::titleChanged) return CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION;
|
||||
if (eventType == AccessibilityEvent::structureChanged) return CONTENT_CHANGE_TYPE_SUBTREE;
|
||||
|
||||
return 0;
|
||||
}();
|
||||
|
||||
sendAccessibilityEventImpl (*this, notification, contentChangeTypes);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::postAnnouncement (const String& announcementString,
|
||||
AnnouncementPriority)
|
||||
{
|
||||
if (! areAnyAccessibilityClientsActive())
|
||||
return;
|
||||
|
||||
const auto rootView = []
|
||||
{
|
||||
LocalRef<jobject> activity (getMainActivity());
|
||||
|
||||
if (activity != nullptr)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> mainWindow (env->CallObjectMethod (activity.get(), AndroidActivity.getWindow));
|
||||
LocalRef<jobject> decorView (env->CallObjectMethod (mainWindow.get(), AndroidWindow.getDecorView));
|
||||
|
||||
return LocalRef<jobject> (env->CallObjectMethod (decorView.get(), AndroidView.getRootView));
|
||||
}
|
||||
|
||||
return LocalRef<jobject>();
|
||||
}();
|
||||
|
||||
if (rootView != nullptr)
|
||||
getEnv()->CallVoidMethod (rootView.get(),
|
||||
AndroidView.announceForAccessibility,
|
||||
javaString (announcementString).get());
|
||||
}
|
||||
|
||||
} // namespace juce
|
570
deps/juce/modules/juce_gui_basics/native/accessibility/juce_ios_Accessibility.mm
vendored
Normal file
570
deps/juce/modules/juce_gui_basics/native/accessibility/juce_ios_Accessibility.mm
vendored
Normal file
@ -0,0 +1,570 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
||||
{
|
||||
|
||||
#if (defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0)
|
||||
#define JUCE_IOS_CONTAINER_API_AVAILABLE 1
|
||||
#endif
|
||||
|
||||
constexpr auto juceUIAccessibilityContainerTypeNone =
|
||||
#if JUCE_IOS_CONTAINER_API_AVAILABLE
|
||||
UIAccessibilityContainerTypeNone;
|
||||
#else
|
||||
0;
|
||||
#endif
|
||||
|
||||
constexpr auto juceUIAccessibilityContainerTypeDataTable =
|
||||
#if JUCE_IOS_CONTAINER_API_AVAILABLE
|
||||
UIAccessibilityContainerTypeDataTable;
|
||||
#else
|
||||
1;
|
||||
#endif
|
||||
|
||||
constexpr auto juceUIAccessibilityContainerTypeList =
|
||||
#if JUCE_IOS_CONTAINER_API_AVAILABLE
|
||||
UIAccessibilityContainerTypeList;
|
||||
#else
|
||||
2;
|
||||
#endif
|
||||
|
||||
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
|
||||
|
||||
//==============================================================================
|
||||
static NSArray* getContainerAccessibilityElements (AccessibilityHandler& handler)
|
||||
{
|
||||
const auto children = handler.getChildren();
|
||||
|
||||
NSMutableArray* accessibleChildren = [NSMutableArray arrayWithCapacity: (NSUInteger) children.size()];
|
||||
|
||||
[accessibleChildren addObject: (id) handler.getNativeImplementation()];
|
||||
|
||||
for (auto* childHandler : children)
|
||||
{
|
||||
id accessibleElement = [&childHandler]
|
||||
{
|
||||
id native = (id) childHandler->getNativeImplementation();
|
||||
|
||||
if (childHandler->getChildren().size() > 0)
|
||||
return [native accessibilityContainer];
|
||||
|
||||
return native;
|
||||
}();
|
||||
|
||||
if (accessibleElement != nil)
|
||||
[accessibleChildren addObject: accessibleElement];
|
||||
}
|
||||
|
||||
return accessibleChildren;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class AccessibilityHandler::AccessibilityNativeImpl
|
||||
{
|
||||
public:
|
||||
explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
|
||||
: accessibilityElement (AccessibilityElement::create (handler))
|
||||
{
|
||||
}
|
||||
|
||||
UIAccessibilityElement* getAccessibilityElement() const noexcept
|
||||
{
|
||||
return accessibilityElement.get();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class AccessibilityContainer : public ObjCClass<UIAccessibilityElement>
|
||||
{
|
||||
public:
|
||||
AccessibilityContainer()
|
||||
: ObjCClass ("JUCEUIAccessibilityElementContainer_")
|
||||
{
|
||||
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
|
||||
addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (CGRect), "@:");
|
||||
addMethod (@selector (accessibilityElements), getAccessibilityElements, "@@:");
|
||||
addMethod (@selector (accessibilityContainerType), getAccessibilityContainerType, "i@:");
|
||||
|
||||
addIvar<AccessibilityHandler*> ("handler");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static AccessibilityHandler* getHandler (id self)
|
||||
{
|
||||
return getIvar<AccessibilityHandler*> (self, "handler");
|
||||
}
|
||||
|
||||
static BOOL getIsAccessibilityElement (id, SEL)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
static CGRect getAccessibilityFrame (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return convertToCGRect (handler->getComponent().getScreenBounds());
|
||||
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
static NSArray* getAccessibilityElements (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return getContainerAccessibilityElements (*handler);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSInteger getAccessibilityContainerType (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (handler->getTableInterface() != nullptr)
|
||||
return juceUIAccessibilityContainerTypeDataTable;
|
||||
|
||||
const auto role = handler->getRole();
|
||||
|
||||
if (role == AccessibilityRole::popupMenu
|
||||
|| role == AccessibilityRole::list
|
||||
|| role == AccessibilityRole::tree)
|
||||
{
|
||||
return juceUIAccessibilityContainerTypeList;
|
||||
}
|
||||
}
|
||||
|
||||
return juceUIAccessibilityContainerTypeNone;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AccessibilityElement : public AccessibleObjCClass<UIAccessibilityElement>
|
||||
{
|
||||
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: (id) handler.getComponent().getWindowHandle()]);
|
||||
object_setInstanceVariable (element.get(), "handler", &handler);
|
||||
return element;
|
||||
}
|
||||
|
||||
AccessibilityElement (Type elementType)
|
||||
{
|
||||
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
|
||||
addMethod (@selector (accessibilityContainer), getAccessibilityContainer, "@@:");
|
||||
addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (CGRect), "@:");
|
||||
addMethod (@selector (accessibilityTraits), getAccessibilityTraits, "i@:");
|
||||
addMethod (@selector (accessibilityLabel), getAccessibilityTitle, "@@:");
|
||||
addMethod (@selector (accessibilityHint), getAccessibilityHelp, "@@:");
|
||||
addMethod (@selector (accessibilityValue), getAccessibilityValue, "@@:");
|
||||
addMethod (@selector (setAccessibilityValue:), setAccessibilityValue, "v@:@");
|
||||
|
||||
addMethod (@selector (accessibilityElementDidBecomeFocused), onFocusGain, "v@:");
|
||||
addMethod (@selector (accessibilityElementDidLoseFocus), onFocusLoss, "v@:");
|
||||
addMethod (@selector (accessibilityElementIsFocused), isFocused, "c@:");
|
||||
addMethod (@selector (accessibilityViewIsModal), getIsAccessibilityModal, "c@:");
|
||||
|
||||
addMethod (@selector (accessibilityActivate), accessibilityPerformActivate, "c@:");
|
||||
addMethod (@selector (accessibilityIncrement), accessibilityPerformIncrement, "c@:");
|
||||
addMethod (@selector (accessibilityDecrement), accessibilityPerformDecrement, "c@:");
|
||||
addMethod (@selector (accessibilityPerformEscape), accessibilityEscape, "c@:");
|
||||
|
||||
addMethod (@selector (accessibilityDataTableCellElementForRow:column:), getAccessibilityDataTableCellElementForRowColumn, "@@:ii");
|
||||
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount, "i@:");
|
||||
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount, "i@:");
|
||||
addProtocol (@protocol (UIAccessibilityContainerDataTable));
|
||||
|
||||
addMethod (@selector (accessibilityRowRange), getAccessibilityRowIndexRange, @encode (NSRange), "@:");
|
||||
addMethod (@selector (accessibilityColumnRange), getAccessibilityColumnIndexRange, @encode (NSRange), "@:");
|
||||
addProtocol (@protocol (UIAccessibilityContainerDataTableCell));
|
||||
|
||||
if (elementType == Type::textElement)
|
||||
{
|
||||
addMethod (@selector (accessibilityLineNumberForPoint:), getAccessibilityLineNumberForPoint, "i@:", @encode (CGPoint));
|
||||
addMethod (@selector (accessibilityContentForLineNumber:), getAccessibilityContentForLineNumber, "@@:i");
|
||||
addMethod (@selector (accessibilityFrameForLineNumber:), getAccessibilityFrameForLineNumber, @encode (CGRect), "@:i");
|
||||
addMethod (@selector (accessibilityPageContent), getAccessibilityPageContent, "@@:");
|
||||
|
||||
addProtocol (@protocol (UIAccessibilityReadingContent));
|
||||
}
|
||||
|
||||
addIvar<UIAccessibilityElement*> ("container");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
static UIAccessibilityElement* getContainer (id self)
|
||||
{
|
||||
return getIvar<UIAccessibilityElement*> (self, "container");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static id getAccessibilityContainer (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (handler->getComponent().isOnDesktop())
|
||||
return (id) handler->getComponent().getWindowHandle();
|
||||
|
||||
if (handler->getChildren().size() > 0)
|
||||
{
|
||||
if (UIAccessibilityElement* container = getContainer (self))
|
||||
return container;
|
||||
|
||||
static AccessibilityContainer cls;
|
||||
|
||||
id windowHandle = (id) handler->getComponent().getWindowHandle();
|
||||
UIAccessibilityElement* container = [cls.createInstance() initWithAccessibilityContainer: windowHandle];
|
||||
|
||||
[container retain];
|
||||
|
||||
object_setInstanceVariable (container, "handler", handler);
|
||||
object_setInstanceVariable (self, "container", container);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
if (auto* parent = handler->getParent())
|
||||
return [(id) parent->getNativeImplementation() accessibilityContainer];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static CGRect getAccessibilityFrame (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return convertToCGRect (handler->getComponent().getScreenBounds());
|
||||
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
static UIAccessibilityTraits getAccessibilityTraits (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::editableText: return UIAccessibilityTraitKeyboardKey;
|
||||
case AccessibilityRole::ignored: return UIAccessibilityTraitNotEnabled;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
static NSString* getAccessibilityValue (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (handler->getCurrentState().isCheckable())
|
||||
return handler->getCurrentState().isChecked() ? @"1" : @"0";
|
||||
|
||||
return (NSString*) getAccessibilityValueFromInterfaces (*handler);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static void onFocusGain (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
const WeakReference<Component> safeComponent (&handler->getComponent());
|
||||
|
||||
performActionIfSupported (self, AccessibilityActionType::focus);
|
||||
|
||||
if (safeComponent != nullptr)
|
||||
handler->grabFocus();
|
||||
}
|
||||
}
|
||||
|
||||
static void onFocusLoss (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
handler->giveAwayFocus();
|
||||
}
|
||||
|
||||
static BOOL isFocused (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return handler->hasFocus (false);
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static BOOL accessibilityPerformActivate (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
// occasionaly 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 (! [(id) handler->getNativeImplementation() isEqual: focusedElement])
|
||||
return [focusedElement accessibilityActivate];
|
||||
|
||||
if (handler->hasFocus (false))
|
||||
return accessibilityPerformPress (self, {});
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static BOOL accessibilityEscape (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self)) {
|
||||
// HACK - look for parent that is a CalloutBox and dismiss it
|
||||
if (CallOutBox* const cb = handler->getComponent().findParentComponentOfClass<CallOutBox>()) {
|
||||
cb->dismiss();
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
static id getAccessibilityDataTableCellElementForRowColumn (id self, SEL, NSUInteger row, NSUInteger column)
|
||||
{
|
||||
if (auto* tableInterface = getTableInterface (self))
|
||||
if (auto* cellHandler = tableInterface->getCellHandler ((int) row, (int) column))
|
||||
return (id) cellHandler->getNativeImplementation();
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSInteger getAccessibilityLineNumberForPoint (id self, SEL, CGPoint point)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* textInterface = handler->getTextInterface())
|
||||
{
|
||||
auto pointInt = roundToIntPoint (point);
|
||||
|
||||
if (handler->getComponent().getScreenBounds().contains (pointInt))
|
||||
{
|
||||
auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
|
||||
|
||||
for (int i = 0; i < textBounds.getNumRectangles(); ++i)
|
||||
if (textBounds.getRectangle (i).contains (pointInt))
|
||||
return (NSInteger) i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NSNotFound;
|
||||
}
|
||||
|
||||
static NSString* getAccessibilityContentForLineNumber (id self, SEL, NSInteger lineNumber)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
{
|
||||
auto lines = StringArray::fromLines (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
|
||||
|
||||
if ((int) lineNumber < lines.size())
|
||||
return juceStringToNS (lines[(int) lineNumber]);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static CGRect getAccessibilityFrameForLineNumber (id self, SEL, NSInteger lineNumber)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
{
|
||||
auto textBounds = textInterface->getTextBounds ({ 0, textInterface->getTotalNumCharacters() });
|
||||
|
||||
if (lineNumber < textBounds.getNumRectangles())
|
||||
return convertToCGRect (textBounds.getRectangle ((int) lineNumber));
|
||||
}
|
||||
|
||||
return CGRectZero;
|
||||
}
|
||||
|
||||
static NSString* getAccessibilityPageContent (id self, SEL)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return juceStringToNS (textInterface->getText ({ 0, textInterface->getTotalNumCharacters() }));
|
||||
|
||||
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 notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
|
||||
{
|
||||
auto notification = [eventType]
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case InternalAccessibilityEvent::elementCreated:
|
||||
case InternalAccessibilityEvent::elementDestroyed:
|
||||
case InternalAccessibilityEvent::elementMovedOrResized:
|
||||
case InternalAccessibilityEvent::focusChanged: return UIAccessibilityLayoutChangedNotification;
|
||||
|
||||
case InternalAccessibilityEvent::windowOpened:
|
||||
case InternalAccessibilityEvent::windowClosed: return UIAccessibilityScreenChangedNotification;
|
||||
}
|
||||
|
||||
return UIAccessibilityNotifications{};
|
||||
}();
|
||||
|
||||
if (notification != UIAccessibilityNotifications{})
|
||||
{
|
||||
const bool moveToHandler = (eventType == InternalAccessibilityEvent::focusChanged && handler.hasFocus (false));
|
||||
|
||||
sendAccessibilityEvent (notification,
|
||||
moveToHandler ? (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, (id) getNativeImplementation());
|
||||
}
|
||||
|
||||
void AccessibilityHandler::postAnnouncement (const String& announcementString, AnnouncementPriority)
|
||||
{
|
||||
sendAccessibilityEvent (UIAccessibilityAnnouncementNotification, juceStringToNS (announcementString));
|
||||
}
|
||||
|
||||
} // namespace juce
|
963
deps/juce/modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm
vendored
Normal file
963
deps/juce/modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm
vendored
Normal file
@ -0,0 +1,963 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 (NSAccessibilityElement<NSAccessibility>*) {}
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
#if (! defined MAC_OS_X_VERSION_10_13) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
|
||||
using NSAccessibilityRole = NSString*;
|
||||
using NSAccessibilityNotificationName = NSString*;
|
||||
#endif
|
||||
|
||||
#define JUCE_NATIVE_ACCESSIBILITY_INCLUDED 1
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunguarded-availability", "-Wunguarded-availability-new")
|
||||
|
||||
//==============================================================================
|
||||
class AccessibilityHandler::AccessibilityNativeImpl
|
||||
{
|
||||
public:
|
||||
explicit AccessibilityNativeImpl (AccessibilityHandler& handler)
|
||||
: accessibilityElement (AccessibilityElement::create (handler))
|
||||
{}
|
||||
|
||||
NSAccessibilityElement<NSAccessibility>* getAccessibilityElement() const noexcept
|
||||
{
|
||||
return accessibilityElement.get();
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class AccessibilityElement : 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), getAccessibilityNotifiesWhenDestroyed, "c@:");
|
||||
addMethod (@selector (isAccessibilityElement), getIsAccessibilityElement, "c@:");
|
||||
addMethod (@selector (isAccessibilityEnabled), getIsAccessibilityEnabled, "c@:");
|
||||
addMethod (@selector (accessibilityWindow), getAccessibilityWindow, "@@:");
|
||||
addMethod (@selector (accessibilityTopLevelUIElement), getAccessibilityWindow, "@@:");
|
||||
addMethod (@selector (accessibilityFocusedUIElement), getAccessibilityFocusedUIElement, "@@:");
|
||||
addMethod (@selector (accessibilityHitTest:), accessibilityHitTest, "@@:", @encode (NSPoint));
|
||||
addMethod (@selector (accessibilityParent), getAccessibilityParent, "@@:");
|
||||
addMethod (@selector (accessibilityChildren), getAccessibilityChildren, "@@:");
|
||||
addMethod (@selector (isAccessibilityFocused), getIsAccessibilityFocused, "c@:");
|
||||
addMethod (@selector (setAccessibilityFocused:), setAccessibilityFocused, "v@:c");
|
||||
addMethod (@selector (isAccessibilityModal), getIsAccessibilityModal, "c@:");
|
||||
addMethod (@selector (accessibilityFrame), getAccessibilityFrame, @encode (NSRect), "@:");
|
||||
addMethod (@selector (accessibilityRole), getAccessibilityRole, "@@:");
|
||||
addMethod (@selector (accessibilitySubrole), getAccessibilitySubrole, "@@:");
|
||||
addMethod (@selector (accessibilityTitle), getAccessibilityTitle, "@@:");
|
||||
addMethod (@selector (accessibilityLabel), getAccessibilityLabel, "@@:");
|
||||
addMethod (@selector (accessibilityHelp), getAccessibilityHelp, "@@:");
|
||||
addMethod (@selector (accessibilityValue), getAccessibilityValue, "@@:");
|
||||
addMethod (@selector (setAccessibilityValue:), setAccessibilityValue, "v@:@");
|
||||
addMethod (@selector (accessibilitySelectedChildren), getAccessibilitySelectedChildren, "@@:");
|
||||
addMethod (@selector (setAccessibilitySelectedChildren:), setAccessibilitySelectedChildren, "v@:@");
|
||||
addMethod (@selector (accessibilityOrientation), getAccessibilityOrientation, "i@:@");
|
||||
|
||||
addMethod (@selector (accessibilityInsertionPointLineNumber), getAccessibilityInsertionPointLineNumber, "i@:");
|
||||
addMethod (@selector (accessibilityVisibleCharacterRange), getAccessibilityVisibleCharacterRange, @encode (NSRange), "@:");
|
||||
addMethod (@selector (accessibilityNumberOfCharacters), getAccessibilityNumberOfCharacters, "i@:");
|
||||
addMethod (@selector (accessibilitySelectedText), getAccessibilitySelectedText, "@@:");
|
||||
addMethod (@selector (accessibilitySelectedTextRange), getAccessibilitySelectedTextRange, @encode (NSRange), "@:");
|
||||
addMethod (@selector (accessibilityAttributedStringForRange:), getAccessibilityAttributedStringForRange, "@@:", @encode (NSRange));
|
||||
addMethod (@selector (accessibilityRangeForLine:), getAccessibilityRangeForLine, @encode (NSRange), "@:i");
|
||||
addMethod (@selector (accessibilityStringForRange:), getAccessibilityStringForRange, "@@:", @encode (NSRange));
|
||||
addMethod (@selector (accessibilityRangeForPosition:), getAccessibilityRangeForPosition, @encode (NSRange), "@:", @encode (NSPoint));
|
||||
addMethod (@selector (accessibilityRangeForIndex:), getAccessibilityRangeForIndex, @encode (NSRange), "@:i");
|
||||
addMethod (@selector (accessibilityFrameForRange:), getAccessibilityFrameForRange, @encode (NSRect), "@:", @encode (NSRange));
|
||||
addMethod (@selector (accessibilityLineForIndex:), getAccessibilityLineForIndex, "i@:i");
|
||||
addMethod (@selector (setAccessibilitySelectedTextRange:), setAccessibilitySelectedTextRange, "v@:", @encode (NSRange));
|
||||
|
||||
addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount, "i@:");
|
||||
addMethod (@selector (accessibilityRows), getAccessibilityRows, "@@:");
|
||||
addMethod (@selector (accessibilitySelectedRows), getAccessibilitySelectedRows, "@@:");
|
||||
addMethod (@selector (setAccessibilitySelectedRows:), setAccessibilitySelectedRows, "v@:@");
|
||||
addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount, "i@:");
|
||||
addMethod (@selector (accessibilityColumns), getAccessibilityColumns, "@@:");
|
||||
addMethod (@selector (accessibilitySelectedColumns), getAccessibilitySelectedColumns, "@@:");
|
||||
addMethod (@selector (setAccessibilitySelectedColumns:), setAccessibilitySelectedColumns, "v@:@");
|
||||
|
||||
addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange, @encode (NSRange), "@:");
|
||||
addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange, @encode (NSRange), "@:");
|
||||
addMethod (@selector (accessibilityIndex), getAccessibilityIndex, "i@:");
|
||||
addMethod (@selector (accessibilityDisclosureLevel), getAccessibilityDisclosureLevel, "i@:");
|
||||
addMethod (@selector (isAccessibilityExpanded), getIsAccessibilityExpanded, "c@:");
|
||||
|
||||
addMethod (@selector (accessibilityPerformIncrement), accessibilityPerformIncrement, "c@:");
|
||||
addMethod (@selector (accessibilityPerformDecrement), accessibilityPerformDecrement, "c@:");
|
||||
addMethod (@selector (accessibilityPerformDelete), accessibilityPerformDelete, "c@:");
|
||||
addMethod (@selector (accessibilityPerformPress), accessibilityPerformPress, "c@:");
|
||||
addMethod (@selector (accessibilityPerformShowMenu), accessibilityPerformShowMenu, "c@:");
|
||||
addMethod (@selector (accessibilityPerformRaise), accessibilityPerformRaise, "c@:");
|
||||
|
||||
addMethod (@selector (isAccessibilitySelectorAllowed:), getIsAccessibilitySelectorAllowed, "c@:@");
|
||||
|
||||
#if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
|
||||
addMethod (@selector (accessibilityChildrenInNavigationOrder), getAccessibilityChildren, "@@:");
|
||||
#endif
|
||||
|
||||
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 setSelectedChildren (NSArray* children, NSArray* selected)
|
||||
{
|
||||
for (id child in children)
|
||||
{
|
||||
if (auto* handler = getHandler (child))
|
||||
{
|
||||
const auto currentState = handler->getCurrentState();
|
||||
const BOOL isSelected = [selected containsObject: child];
|
||||
|
||||
if (isSelectable (currentState))
|
||||
{
|
||||
if (currentState.isSelected() != isSelected)
|
||||
handler->getActions().invoke (AccessibilityActionType::toggle);
|
||||
}
|
||||
else if (currentState.isFocusable())
|
||||
{
|
||||
[child setAccessibilityFocused: isSelected];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static BOOL getAccessibilityNotifiesWhenDestroyed (id, SEL) { return YES; }
|
||||
|
||||
static BOOL getIsAccessibilityEnabled (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return handler->getComponent().isEnabled();
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
static id getAccessibilityWindow (id self, SEL)
|
||||
{
|
||||
return [[self accessibilityParent] accessibilityWindow];
|
||||
}
|
||||
|
||||
static id getAccessibilityFocusedUIElement (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* modal = Component::getCurrentlyModalComponent())
|
||||
{
|
||||
const auto& component = handler->getComponent();
|
||||
|
||||
if (! component.isParentOf (modal)
|
||||
&& component.isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if (auto* modalHandler = modal->getAccessibilityHandler())
|
||||
{
|
||||
if (auto* focusChild = modalHandler->getChildFocus())
|
||||
return (id) focusChild->getNativeImplementation();
|
||||
|
||||
return (id) modalHandler->getNativeImplementation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* focusChild = handler->getChildFocus())
|
||||
return (id) focusChild->getNativeImplementation();
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static id accessibilityHitTest (id self, SEL, NSPoint point)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* child = handler->getChildAt (roundToIntPoint (flippedScreenPoint (point))))
|
||||
return (id) child->getNativeImplementation();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static id getAccessibilityParent (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* parentHandler = handler->getParent())
|
||||
return NSAccessibilityUnignoredAncestor ((id) parentHandler->getNativeImplementation());
|
||||
|
||||
return NSAccessibilityUnignoredAncestor ((id) handler->getComponent().getWindowHandle());
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
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: (id) childHandler->getNativeImplementation()];
|
||||
|
||||
return accessibleChildren;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static id getAccessibilityValue (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (handler->getCurrentState().isCheckable())
|
||||
return handler->getCurrentState().isChecked() ? @(1) : @(0);
|
||||
|
||||
return getAccessibilityValueFromInterfaces (*handler);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSArray* getAccessibilitySelectedChildren (id self, SEL)
|
||||
{
|
||||
return getSelectedChildren ([self accessibilityChildren]);
|
||||
}
|
||||
|
||||
static void setAccessibilitySelectedChildren (id self, SEL, NSArray* selected)
|
||||
{
|
||||
setSelectedChildren ([self accessibilityChildren], selected);
|
||||
}
|
||||
|
||||
static NSAccessibilityOrientation getAccessibilityOrientation (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return handler->getComponent().getBounds().toFloat().getAspectRatio() > 1.0f
|
||||
? NSAccessibilityOrientationHorizontal
|
||||
: NSAccessibilityOrientationVertical;
|
||||
|
||||
return NSAccessibilityOrientationUnknown;
|
||||
}
|
||||
|
||||
static BOOL getIsAccessibilityFocused (id self, SEL)
|
||||
{
|
||||
return [[self accessibilityWindow] accessibilityFocusedUIElement] == self;
|
||||
}
|
||||
|
||||
static void 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static NSRect getAccessibilityFrame (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return flippedScreenRect (makeNSRect (handler->getComponent().getScreenBounds()));
|
||||
|
||||
return NSZeroRect;
|
||||
}
|
||||
|
||||
static NSAccessibilityRole getAccessibilityRole (id self, SEL)
|
||||
{
|
||||
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 NSAccessibilityListRole;
|
||||
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;
|
||||
}
|
||||
|
||||
static NSAccessibilityRole getAccessibilitySubrole (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
if (textInterface->isDisplayingProtectedText())
|
||||
return NSAccessibilitySecureTextFieldSubrole;
|
||||
|
||||
const auto role = handler->getRole();
|
||||
|
||||
if (role == AccessibilityRole::window) return NSAccessibilityStandardWindowSubrole;
|
||||
if (role == AccessibilityRole::dialogWindow) return NSAccessibilityDialogSubrole;
|
||||
if (role == AccessibilityRole::tooltip
|
||||
|| role == AccessibilityRole::splashScreen) return NSAccessibilityFloatingWindowSubrole;
|
||||
if (role == AccessibilityRole::toggleButton) return NSAccessibilityToggleSubrole;
|
||||
if (role == AccessibilityRole::treeItem
|
||||
|| role == AccessibilityRole::listItem) return NSAccessibilityOutlineRowSubrole;
|
||||
if (role == AccessibilityRole::row && getCellInterface (self) != nullptr) return NSAccessibilityTableRowSubrole;
|
||||
|
||||
const auto& component = handler->getComponent();
|
||||
|
||||
if (auto* documentWindow = component.findParentComponentOfClass<DocumentWindow>())
|
||||
{
|
||||
if (role == AccessibilityRole::button)
|
||||
{
|
||||
if (&component == documentWindow->getCloseButton()) return NSAccessibilityCloseButtonSubrole;
|
||||
if (&component == documentWindow->getMinimiseButton()) return NSAccessibilityMinimizeButtonSubrole;
|
||||
if (&component == documentWindow->getMaximiseButton()) return NSAccessibilityFullScreenButtonSubrole;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NSAccessibilityUnknownRole;
|
||||
}
|
||||
|
||||
static NSString* getAccessibilityLabel (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return juceStringToNS (handler->getDescription());
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static NSInteger getAccessibilityInsertionPointLineNumber (id self, SEL)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return [self accessibilityLineForIndex: textInterface->getTextInsertionOffset()];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NSRange getAccessibilityVisibleCharacterRange (id self, SEL)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return juceRangeToNS ({ 0, textInterface->getTotalNumCharacters() });
|
||||
|
||||
return NSMakeRange (0, 0);
|
||||
}
|
||||
|
||||
static NSInteger getAccessibilityNumberOfCharacters (id self, SEL)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return textInterface->getTotalNumCharacters();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NSString* getAccessibilitySelectedText (id self, SEL)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return juceStringToNS (textInterface->getText (textInterface->getSelection()));
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSRange getAccessibilitySelectedTextRange (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);
|
||||
}
|
||||
|
||||
static NSAttributedString* getAccessibilityAttributedStringForRange (id self, SEL, NSRange range)
|
||||
{
|
||||
NSString* string = [self accessibilityStringForRange: range];
|
||||
|
||||
if (string != nil)
|
||||
return [[[NSAttributedString alloc] initWithString: string] autorelease];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSRange getAccessibilityRangeForLine (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);
|
||||
}
|
||||
|
||||
static NSString* getAccessibilityStringForRange (id self, SEL, NSRange range)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return juceStringToNS (textInterface->getText (nsRangeToJuce (range)));
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSRange getAccessibilityRangeForPosition (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);
|
||||
}
|
||||
|
||||
static NSRange getAccessibilityRangeForIndex (id self, SEL, NSInteger index)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
if (isPositiveAndBelow (index, textInterface->getTotalNumCharacters()))
|
||||
return NSMakeRange ((NSUInteger) index, 1);
|
||||
|
||||
return NSMakeRange (0, 0);
|
||||
}
|
||||
|
||||
static NSRect getAccessibilityFrameForRange (id self, SEL, NSRange range)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
return flippedScreenRect (makeNSRect (textInterface->getTextBounds (nsRangeToJuce (range)).getBounds()));
|
||||
|
||||
return NSZeroRect;
|
||||
}
|
||||
|
||||
static NSInteger getAccessibilityLineForIndex (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;
|
||||
}
|
||||
|
||||
static void setAccessibilitySelectedTextRange (id self, SEL, NSRange selectedRange)
|
||||
{
|
||||
if (auto* textInterface = getTextInterface (self))
|
||||
textInterface->setSelection (nsRangeToJuce (selectedRange));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static NSArray* getAccessibilityRows (id self, SEL)
|
||||
{
|
||||
NSMutableArray* rows = [[NSMutableArray new] autorelease];
|
||||
|
||||
if (auto* tableInterface = getTableInterface (self))
|
||||
{
|
||||
for (int row = 0; row < tableInterface->getNumRows(); ++row)
|
||||
{
|
||||
if (auto* handler = tableInterface->getCellHandler (row, 0))
|
||||
{
|
||||
[rows addObject: (id) handler->getNativeImplementation()];
|
||||
}
|
||||
else
|
||||
{
|
||||
[rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole
|
||||
frame: NSZeroRect
|
||||
label: @"Offscreen Row"
|
||||
parent: self]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
static NSArray* getAccessibilitySelectedRows (id self, SEL)
|
||||
{
|
||||
return getSelectedChildren ([self accessibilityRows]);
|
||||
}
|
||||
|
||||
static void setAccessibilitySelectedRows (id self, SEL, NSArray* selected)
|
||||
{
|
||||
setSelectedChildren ([self accessibilityRows], selected);
|
||||
}
|
||||
|
||||
static NSArray* getAccessibilityColumns (id self, SEL)
|
||||
{
|
||||
NSMutableArray* columns = [[NSMutableArray new] autorelease];
|
||||
|
||||
if (auto* tableInterface = getTableInterface (self))
|
||||
{
|
||||
for (int column = 0; column < tableInterface->getNumColumns(); ++column)
|
||||
{
|
||||
if (auto* handler = tableInterface->getCellHandler (0, column))
|
||||
{
|
||||
[columns addObject: (id) handler->getNativeImplementation()];
|
||||
}
|
||||
else
|
||||
{
|
||||
[columns addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityColumnRole
|
||||
frame: NSZeroRect
|
||||
label: @"Offscreen Column"
|
||||
parent: self]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
static NSArray* getAccessibilitySelectedColumns (id self, SEL)
|
||||
{
|
||||
return getSelectedChildren ([self accessibilityColumns]);
|
||||
}
|
||||
|
||||
static void setAccessibilitySelectedColumns (id self, SEL, NSArray* selected)
|
||||
{
|
||||
setSelectedChildren ([self accessibilityColumns], selected);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static NSInteger getAccessibilityIndex (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
if (auto* cellInterface = handler->getCellInterface())
|
||||
{
|
||||
NSAccessibilityRole role = [self accessibilityRole];
|
||||
|
||||
if ([role isEqual: NSAccessibilityRowRole])
|
||||
return cellInterface->getRowIndex();
|
||||
|
||||
if ([role isEqual: NSAccessibilityColumnRole])
|
||||
return cellInterface->getColumnIndex();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NSInteger getAccessibilityDisclosureLevel (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
if (auto* cellInterface = handler->getCellInterface())
|
||||
return cellInterface->getDisclosureLevel();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL getIsAccessibilityExpanded (id self, SEL)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
return handler->getCurrentState().isExpanded();
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static BOOL accessibilityPerformShowMenu (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::showMenu); }
|
||||
static BOOL accessibilityPerformRaise (id self, SEL) { [self setAccessibilityFocused: YES]; return YES; }
|
||||
|
||||
static BOOL 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;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static BOOL getIsAccessibilitySelectorAllowed (id self, SEL, SEL selector)
|
||||
{
|
||||
if (auto* handler = getHandler (self))
|
||||
{
|
||||
const auto role = 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 (accessibilityColumns),
|
||||
@selector (accessibilitySelectedColumns) })
|
||||
{
|
||||
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->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 role == AccessibilityRole::popupMenu;
|
||||
|
||||
if (selector == @selector (accessibilityOrientation))
|
||||
return role == AccessibilityRole::scrollBar;
|
||||
|
||||
if (selector == @selector (isAccessibilityExpanded))
|
||||
return currentState.isExpandable();
|
||||
|
||||
return sendSuperclassMessage<BOOL> (self, @selector (isAccessibilitySelectorAllowed:), selector);
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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()
|
||||
{
|
||||
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 (id accessibilityElement = (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 notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
|
||||
{
|
||||
auto notification = [eventType]
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case InternalAccessibilityEvent::elementCreated: return NSAccessibilityCreatedNotification;
|
||||
case InternalAccessibilityEvent::elementDestroyed: return NSAccessibilityUIElementDestroyedNotification;
|
||||
case InternalAccessibilityEvent::elementMovedOrResized: return layoutChangedNotification();
|
||||
case InternalAccessibilityEvent::focusChanged: return NSAccessibilityFocusedUIElementChangedNotification;
|
||||
case InternalAccessibilityEvent::windowOpened: return NSAccessibilityWindowCreatedNotification;
|
||||
case InternalAccessibilityEvent::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.10, *))
|
||||
{
|
||||
auto nsPriority = [priority]
|
||||
{
|
||||
switch (priority)
|
||||
{
|
||||
case AnnouncementPriority::low: return NSAccessibilityPriorityLow;
|
||||
case AnnouncementPriority::medium: return NSAccessibilityPriorityMedium;
|
||||
case AnnouncementPriority::high: return NSAccessibilityPriorityHigh;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return NSAccessibilityPriorityLow;
|
||||
}();
|
||||
|
||||
sendAccessibilityEvent ((id) [NSApp mainWindow],
|
||||
NSAccessibilityAnnouncementRequestedNotification,
|
||||
@{ NSAccessibilityAnnouncementKey: juceStringToNS (announcementString),
|
||||
NSAccessibilityPriorityKey: @(nsPriority) });
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
} // namespace juce
|
249
deps/juce/modules/juce_gui_basics/native/accessibility/juce_mac_AccessibilitySharedCode.mm
vendored
Normal file
249
deps/juce/modules/juce_gui_basics/native/accessibility/juce_mac_AccessibilitySharedCode.mm
vendored
Normal file
@ -0,0 +1,249 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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() : ObjCClass<Base> ("JUCEAccessibilityElement_")
|
||||
{
|
||||
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)
|
||||
{
|
||||
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 = getAccessibleApplicationOrPluginName();
|
||||
|
||||
NSString* nsString = juceStringToNS (title);
|
||||
|
||||
#if ! JUCE_IOS
|
||||
if (nsString != nil && [[self accessibilityValue] isEqual: nsString])
|
||||
return @"";
|
||||
#endif
|
||||
|
||||
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* tableInterface = getTableInterface (self))
|
||||
return tableInterface->getNumRows();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NSInteger getAccessibilityColumnCount (id self, SEL)
|
||||
{
|
||||
if (auto* tableInterface = getTableInterface (self))
|
||||
return tableInterface->getNumColumns();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NSRange getAccessibilityRowIndexRange (id self, SEL)
|
||||
{
|
||||
if (auto* cellInterface = getCellInterface (self))
|
||||
return NSMakeRange ((NSUInteger) cellInterface->getRowIndex(),
|
||||
(NSUInteger) cellInterface->getRowSpan());
|
||||
|
||||
return NSMakeRange (0, 0);
|
||||
}
|
||||
|
||||
static NSRange getAccessibilityColumnIndexRange (id self, SEL)
|
||||
{
|
||||
if (auto* cellInterface = getCellInterface (self))
|
||||
return NSMakeRange ((NSUInteger) cellInterface->getColumnIndex(),
|
||||
(NSUInteger) cellInterface->getColumnSpan());
|
||||
|
||||
return NSMakeRange (0, 0);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibleObjCClass)
|
||||
};
|
||||
|
||||
}
|
301
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_Accessibility.cpp
vendored
Normal file
301
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_Accessibility.cpp
vendored
Normal file
@ -0,0 +1,301 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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()
|
||||
{
|
||||
accessibilityElement->invalidateElement();
|
||||
--providerCount;
|
||||
|
||||
if (auto* uiaWrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
{
|
||||
ComSmartPtr<IRawElementProviderSimple> provider;
|
||||
accessibilityElement->QueryInterface (IID_PPV_ARGS (provider.resetAndGetPointerAddress()));
|
||||
|
||||
uiaWrapper->disconnectProvider (provider);
|
||||
|
||||
if (providerCount == 0)
|
||||
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 notifyAccessibilityEventInternal (const AccessibilityHandler& handler, InternalAccessibilityEvent eventType)
|
||||
{
|
||||
if (eventType == InternalAccessibilityEvent::elementCreated
|
||||
|| eventType == InternalAccessibilityEvent::elementDestroyed)
|
||||
{
|
||||
if (auto* parent = handler.getParent())
|
||||
sendAccessibilityAutomationEvent (*parent, UIA_LayoutInvalidatedEventId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto event = [eventType]() -> EVENTID
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case InternalAccessibilityEvent::focusChanged: return UIA_AutomationFocusChangedEventId;
|
||||
case InternalAccessibilityEvent::windowOpened: return UIA_Window_WindowOpenedEventId;
|
||||
case InternalAccessibilityEvent::windowClosed: return UIA_Window_WindowClosedEventId;
|
||||
case InternalAccessibilityEvent::elementCreated:
|
||||
case InternalAccessibilityEvent::elementDestroyed:
|
||||
case InternalAccessibilityEvent::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);
|
||||
}
|
||||
|
||||
auto event = [eventType]() -> EVENTID
|
||||
{
|
||||
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 : public DeletedAtShutdown
|
||||
{
|
||||
SpVoiceWrapper()
|
||||
{
|
||||
auto hr = voice.CoCreateInstance (CLSID_SpVoice);
|
||||
|
||||
jassertquiet (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
|
||||
{
|
||||
long getUiaRootObjectId()
|
||||
{
|
||||
return static_cast<long> (UiaRootObjectId);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
553
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.cpp
vendored
Normal file
553
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.cpp
vendored
Normal file
@ -0,0 +1,553 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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;
|
||||
|
||||
//==============================================================================
|
||||
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)
|
||||
{
|
||||
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_HeaderItemControlTypeId;
|
||||
|
||||
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 (IRawElementProviderFragmentRoot) && ! isFragmentRoot()))
|
||||
return E_NOINTERFACE;
|
||||
|
||||
return ComBaseClassHelper::QueryInterface (refId, result);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::get_HostRawElementProvider (IRawElementProviderSimple** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
if (isFragmentRoot())
|
||||
if (auto* wrapper = WindowsUIAWrapper::getInstanceWithoutCreating())
|
||||
return wrapper->hostProviderFromHwnd ((HWND) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::get_ProviderOptions (ProviderOptions* options)
|
||||
{
|
||||
if (options == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
*options = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading;
|
||||
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();
|
||||
|
||||
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.getActions().contains (AccessibilityActionType::toggle)
|
||||
&& accessibilityHandler.getCurrentState().isCheckable())
|
||||
{
|
||||
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_GridPatternId:
|
||||
{
|
||||
if (accessibilityHandler.getTableInterface() != nullptr)
|
||||
return new UIAGridProvider (this);
|
||||
|
||||
break;
|
||||
}
|
||||
case UIA_GridItemPatternId:
|
||||
{
|
||||
if (accessibilityHandler.getCellInterface() != nullptr)
|
||||
return 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;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}();
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::GetPropertyValue (PROPERTYID propertyId, VARIANT* pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
VariantHelpers::clear (pRetVal);
|
||||
|
||||
const auto fragmentRoot = isFragmentRoot();
|
||||
|
||||
const auto role = accessibilityHandler.getRole();
|
||||
const auto state = accessibilityHandler.getCurrentState();
|
||||
|
||||
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 (! accessibilityHandler.isIgnored() && 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:
|
||||
VariantHelpers::setString (getElementName(), pRetVal);
|
||||
break;
|
||||
case UIA_ProcessIdPropertyId:
|
||||
VariantHelpers::setInt ((int) GetCurrentProcessId(), pRetVal);
|
||||
break;
|
||||
case UIA_NativeWindowHandlePropertyId:
|
||||
if (fragmentRoot)
|
||||
VariantHelpers::setInt ((int) (pointer_sized_int) accessibilityHandler.getComponent().getWindowHandle(), pRetVal);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AccessibilityNativeHandle::Navigate (NavigateDirection direction, IRawElementProviderFragment** pRetVal)
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto* handler = [&]() -> AccessibilityHandler*
|
||||
{
|
||||
if (direction == NavigateDirection_Parent)
|
||||
return accessibilityHandler.getParent();
|
||||
|
||||
if (direction == NavigateDirection_FirstChild
|
||||
|| direction == NavigateDirection_LastChild)
|
||||
{
|
||||
auto children = accessibilityHandler.getChildren();
|
||||
|
||||
return children.empty() ? nullptr
|
||||
: (direction == NavigateDirection_FirstChild ? children.front()
|
||||
: children.back());
|
||||
}
|
||||
|
||||
if (direction == NavigateDirection_NextSibling
|
||||
|| direction == 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 == NavigateDirection_NextSibling && iter != std::prev (siblings.cend()))
|
||||
return *std::next (iter);
|
||||
|
||||
if (direction == 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 (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 (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, 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 (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;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
String AccessibilityNativeHandle::getElementName() const
|
||||
{
|
||||
if (accessibilityHandler.getRole() == AccessibilityRole::tooltip)
|
||||
return accessibilityHandler.getDescription();
|
||||
|
||||
auto name = accessibilityHandler.getTitle();
|
||||
|
||||
if (name.isEmpty() && isFragmentRoot())
|
||||
return getAccessibleApplicationOrPluginName();
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
} // namespace juce
|
80
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.h
vendored
Normal file
80
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_AccessibilityElement.h
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 UIA_FullDescriptionPropertyId 30159
|
||||
#define UIA_IsDialogPropertyId 30174
|
||||
|
||||
class AccessibilityNativeHandle : public ComBaseClassHelper<IRawElementProviderSimple,
|
||||
IRawElementProviderFragment,
|
||||
IRawElementProviderFragmentRoot>
|
||||
{
|
||||
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 (NavigateDirection direction, IRawElementProviderFragment** pRetVal) override;
|
||||
JUCE_COMRESULT GetRuntimeId (SAFEARRAY** pRetVal) override;
|
||||
JUCE_COMRESULT get_BoundingRectangle (UiaRect* pRetVal) override;
|
||||
JUCE_COMRESULT GetEmbeddedFragmentRoots (SAFEARRAY** pRetVal) override;
|
||||
JUCE_COMRESULT SetFocus() override;
|
||||
JUCE_COMRESULT get_FragmentRoot (IRawElementProviderFragmentRoot** pRetVal) override;
|
||||
|
||||
JUCE_COMRESULT ElementProviderFromPoint (double x, double y, IRawElementProviderFragment** pRetVal) override;
|
||||
JUCE_COMRESULT GetFocus (IRawElementProviderFragment** 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)
|
||||
};
|
||||
|
||||
}
|
86
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAExpandCollapseProvider.h
vendored
Normal file
86
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAExpandCollapseProvider.h
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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<IExpandCollapseProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAExpandCollapseProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Expand() override
|
||||
{
|
||||
return invokeShowMenu();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Collapse() override
|
||||
{
|
||||
return invokeShowMenu();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ExpandCollapseState (ExpandCollapseState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = getHandler().getCurrentState().isExpanded()
|
||||
? ExpandCollapseState_Expanded
|
||||
: 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))
|
||||
{
|
||||
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
|
105
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAGridItemProvider.h
vendored
Normal file
105
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAGridItemProvider.h
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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<IGridItemProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAGridItemProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT get_Row (int* pRetVal) override
|
||||
{
|
||||
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
|
||||
{
|
||||
*pRetVal = cellInterface.getRowIndex();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_Column (int* pRetVal) override
|
||||
{
|
||||
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
|
||||
{
|
||||
*pRetVal = cellInterface.getColumnIndex();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_RowSpan (int* pRetVal) override
|
||||
{
|
||||
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
|
||||
{
|
||||
*pRetVal = cellInterface.getRowSpan();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ColumnSpan (int* pRetVal) override
|
||||
{
|
||||
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
|
||||
{
|
||||
*pRetVal = cellInterface.getColumnSpan();
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ContainingGrid (IRawElementProviderSimple** pRetVal) override
|
||||
{
|
||||
return withCellInterface (pRetVal, [&] (const AccessibilityCellInterface& cellInterface)
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
if (auto* handler = cellInterface.getTableHandler())
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Value, typename Callback>
|
||||
JUCE_COMRESULT withCellInterface (Value* pRetVal, Callback&& callback) const
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* cellInterface = getHandler().getCellInterface())
|
||||
{
|
||||
callback (*cellInterface);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridItemProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
94
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAGridProvider.h
vendored
Normal file
94
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAGridProvider.h
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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<IGridProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAGridProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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* handler = tableInterface.getCellHandler (row, column))
|
||||
handler->getNativeImplementation()->QueryInterface (IID_PPV_ARGS (pRetVal));
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Value, typename Callback>
|
||||
JUCE_COMRESULT withTableInterface (Value* pRetVal, Callback&& callback) const
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* tableInterface = getHandler().getTableInterface())
|
||||
return callback (*tableInterface);
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAGridProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
105
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h
vendored
Normal file
105
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAHelpers.h
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
||||
{
|
||||
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 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
|
62
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAInvokeProvider.h
vendored
Normal file
62
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAInvokeProvider.h
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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<IInvokeProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAInvokeProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Invoke() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (handler.getActions().invoke (AccessibilityActionType::press))
|
||||
{
|
||||
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
|
58
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAProviderBase.h
vendored
Normal file
58
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAProviderBase.h
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAProviders.h
vendored
Normal file
43
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAProviders.h
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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);
|
||||
}
|
||||
|
||||
#include "juce_win32_UIAProviderBase.h"
|
||||
#include "juce_win32_UIAExpandCollapseProvider.h"
|
||||
#include "juce_win32_UIAGridItemProvider.h"
|
||||
#include "juce_win32_UIAGridProvider.h"
|
||||
#include "juce_win32_UIAInvokeProvider.h"
|
||||
#include "juce_win32_UIARangeValueProvider.h"
|
||||
#include "juce_win32_UIASelectionProvider.h"
|
||||
#include "juce_win32_UIATextProvider.h"
|
||||
#include "juce_win32_UIAToggleProvider.h"
|
||||
#include "juce_win32_UIATransformProvider.h"
|
||||
#include "juce_win32_UIAValueProvider.h"
|
||||
#include "juce_win32_UIAWindowProvider.h"
|
140
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIARangeValueProvider.h
vendored
Normal file
140
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIARangeValueProvider.h
vendored
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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<IRangeValueProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIARangeValueProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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
|
256
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIASelectionProvider.h
vendored
Normal file
256
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIASelectionProvider.h
vendored
Normal file
@ -0,0 +1,256 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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")
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class UIASelectionItemProvider : public UIAProviderBase,
|
||||
public ComBaseClassHelper<ISelectionItemProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIASelectionItemProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle),
|
||||
isRadioButton (getHandler().getRole() == AccessibilityRole::radioButton)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT AddToSelection() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (isRadioButton)
|
||||
{
|
||||
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<ISelectionProvider2>
|
||||
{
|
||||
public:
|
||||
explicit UIASelectionProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
|
||||
{
|
||||
if (iid == _uuidof (IUnknown) || iid == _uuidof (ISelectionProvider))
|
||||
return castToType<ISelectionProvider> (result);
|
||||
|
||||
if (iid == _uuidof (ISelectionProvider2))
|
||||
return castToType<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
|
592
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h
vendored
Normal file
592
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIATextProvider.h
vendored
Normal file
@ -0,0 +1,592 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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<ITextProvider2>
|
||||
{
|
||||
public:
|
||||
explicit UIATextProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT QueryInterface (REFIID iid, void** result) override
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
|
||||
|
||||
if (iid == _uuidof (IUnknown) || iid == _uuidof (ITextProvider))
|
||||
return castToType<ITextProvider> (result);
|
||||
|
||||
if (iid == _uuidof (ITextProvider2))
|
||||
return castToType<ITextProvider2> (result);
|
||||
|
||||
*result = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
JUCE_COMRESULT get_DocumentRange (ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
*pRetVal = new UIATextRangeProvider (*this, { 0, textInterface.getTotalNumCharacters() });
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_SupportedTextSelection (SupportedTextSelection* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = 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*, ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT RangeFromPoint (UiaPoint point, 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, 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*, 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<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 (ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = new UIATextRangeProvider (*owner, selectionRange);
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT Compare (ITextRangeProvider* range, BOOL* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = (selectionRange == static_cast<UIATextRangeProvider*> (range)->getSelectionRange());
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT CompareEndpoints (TextPatternRangeEndpoint endpoint,
|
||||
ITextRangeProvider* targetRange,
|
||||
TextPatternRangeEndpoint targetEndpoint,
|
||||
int* pRetVal) override
|
||||
{
|
||||
if (targetRange == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
auto offset = (endpoint == TextPatternRangeEndpoint_Start ? selectionRange.getStart()
|
||||
: selectionRange.getEnd());
|
||||
|
||||
auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
|
||||
auto otherOffset = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart()
|
||||
: otherRange.getEnd());
|
||||
|
||||
*pRetVal = offset - otherOffset;
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT ExpandToEnclosingUnit (TextUnit unit) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* textInterface = owner->getHandler().getTextInterface())
|
||||
{
|
||||
const auto boundaryType = getBoundaryType (unit);
|
||||
|
||||
const auto start = AccessibilityTextHelpers::findTextBoundary (*textInterface,
|
||||
selectionRange.getStart(),
|
||||
boundaryType,
|
||||
AccessibilityTextHelpers::Direction::backwards);
|
||||
|
||||
const auto end = AccessibilityTextHelpers::findTextBoundary (*textInterface,
|
||||
start,
|
||||
boundaryType,
|
||||
AccessibilityTextHelpers::Direction::forwards);
|
||||
|
||||
selectionRange = Range<int> (start, end);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT FindAttribute (TEXTATTRIBUTEID, VARIANT, BOOL, ITextRangeProvider** pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, []
|
||||
{
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT FindText (BSTR text, BOOL backward, BOOL ignoreCase,
|
||||
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);
|
||||
|
||||
switch (attributeId)
|
||||
{
|
||||
case UIA_IsReadOnlyAttributeId:
|
||||
{
|
||||
VariantHelpers::setBool (textInterface.isReadOnly(), pRetVal);
|
||||
break;
|
||||
}
|
||||
|
||||
case UIA_CaretPositionAttributeId:
|
||||
{
|
||||
auto cursorPos = textInterface.getTextInsertionOffset();
|
||||
|
||||
auto caretPos = [&]
|
||||
{
|
||||
if (cursorPos == 0)
|
||||
return CaretPosition_BeginningOfLine;
|
||||
|
||||
if (cursorPos == textInterface.getTotalNumCharacters())
|
||||
return CaretPosition_EndOfLine;
|
||||
|
||||
return 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 (TextUnit unit, int count, int* pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface&)
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
MoveEndpointByUnit (TextPatternRangeEndpoint_End, unit, count, pRetVal);
|
||||
MoveEndpointByUnit (TextPatternRangeEndpoint_Start, unit, count, pRetVal);
|
||||
}
|
||||
else if (count < 0)
|
||||
{
|
||||
MoveEndpointByUnit (TextPatternRangeEndpoint_Start, unit, count, pRetVal);
|
||||
MoveEndpointByUnit (TextPatternRangeEndpoint_End, unit, count, pRetVal);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT MoveEndpointByRange (TextPatternRangeEndpoint endpoint,
|
||||
ITextRangeProvider* targetRange,
|
||||
TextPatternRangeEndpoint targetEndpoint) override
|
||||
{
|
||||
if (targetRange == nullptr)
|
||||
return E_INVALIDARG;
|
||||
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* textInterface = owner->getHandler().getTextInterface())
|
||||
{
|
||||
auto otherRange = static_cast<UIATextRangeProvider*> (targetRange)->getSelectionRange();
|
||||
auto targetPoint = (targetEndpoint == TextPatternRangeEndpoint_Start ? otherRange.getStart()
|
||||
: otherRange.getEnd());
|
||||
|
||||
setEndpointChecked (endpoint, targetPoint);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT MoveEndpointByUnit (TextPatternRangeEndpoint endpoint,
|
||||
TextUnit unit,
|
||||
int count,
|
||||
int* pRetVal) override
|
||||
{
|
||||
return owner->withTextInterface (pRetVal, [&] (const AccessibilityTextInterface& textInterface)
|
||||
{
|
||||
if (count == 0 || textInterface.getTotalNumCharacters() == 0)
|
||||
return S_OK;
|
||||
|
||||
auto endpointToMove = (endpoint == TextPatternRangeEndpoint_Start ? selectionRange.getStart()
|
||||
: selectionRange.getEnd());
|
||||
|
||||
const auto direction = (count > 0 ? AccessibilityTextHelpers::Direction::forwards
|
||||
: AccessibilityTextHelpers::Direction::backwards);
|
||||
|
||||
const auto boundaryType = getBoundaryType (unit);
|
||||
|
||||
// handle case where endpoint is on a boundary
|
||||
if (AccessibilityTextHelpers::findTextBoundary (textInterface, endpointToMove, boundaryType, direction) == endpointToMove)
|
||||
endpointToMove += (direction == AccessibilityTextHelpers::Direction::forwards ? 1 : -1);
|
||||
|
||||
int numMoved;
|
||||
for (numMoved = 0; numMoved < std::abs (count); ++numMoved)
|
||||
{
|
||||
auto nextEndpoint = AccessibilityTextHelpers::findTextBoundary (textInterface,
|
||||
endpointToMove,
|
||||
boundaryType,
|
||||
direction);
|
||||
|
||||
if (nextEndpoint == endpointToMove)
|
||||
break;
|
||||
|
||||
endpointToMove = nextEndpoint;
|
||||
}
|
||||
|
||||
*pRetVal = numMoved;
|
||||
setEndpointChecked (endpoint, endpointToMove);
|
||||
|
||||
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 (TextUnit unit)
|
||||
{
|
||||
switch (unit)
|
||||
{
|
||||
case TextUnit_Character:
|
||||
return AccessibilityTextHelpers::BoundaryType::character;
|
||||
|
||||
case TextUnit_Format:
|
||||
case TextUnit_Word:
|
||||
return AccessibilityTextHelpers::BoundaryType::word;
|
||||
|
||||
case TextUnit_Line:
|
||||
return AccessibilityTextHelpers::BoundaryType::line;
|
||||
|
||||
case TextUnit_Paragraph:
|
||||
case TextUnit_Page:
|
||||
case TextUnit_Document:
|
||||
return AccessibilityTextHelpers::BoundaryType::document;
|
||||
};
|
||||
|
||||
jassertfalse;
|
||||
return AccessibilityTextHelpers::BoundaryType::character;
|
||||
}
|
||||
|
||||
void setEndpointChecked (TextPatternRangeEndpoint endpoint, int newEndpoint)
|
||||
{
|
||||
if (endpoint == 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
|
80
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAToggleProvider.h
vendored
Normal file
80
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAToggleProvider.h
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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<IToggleProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAToggleProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT Toggle() override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
const auto& handler = getHandler();
|
||||
|
||||
if (handler.getActions().invoke (AccessibilityActionType::toggle))
|
||||
{
|
||||
VARIANT newValue;
|
||||
VariantHelpers::setInt (getCurrentToggleState(), &newValue);
|
||||
|
||||
sendAccessibilityPropertyChangedEvent (handler, UIA_ToggleToggleStatePropertyId, newValue);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_ToggleState (ToggleState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]
|
||||
{
|
||||
*pRetVal = getCurrentToggleState();
|
||||
return S_OK;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
ToggleState getCurrentToggleState() const
|
||||
{
|
||||
return getHandler().getCurrentState().isChecked() ? ToggleState_On
|
||||
: ToggleState_Off;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIAToggleProvider)
|
||||
};
|
||||
|
||||
} // namespace juce
|
125
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIATransformProvider.h
vendored
Normal file
125
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIATransformProvider.h
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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<ITransformProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIATransformProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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
|
86
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAValueProvider.h
vendored
Normal file
86
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAValueProvider.h
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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<IValueProvider>
|
||||
{
|
||||
public:
|
||||
UIAValueProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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
|
197
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAWindowProvider.h
vendored
Normal file
197
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_UIAWindowProvider.h
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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<IWindowProvider>
|
||||
{
|
||||
public:
|
||||
explicit UIAWindowProvider (AccessibilityNativeHandle* nativeHandle)
|
||||
: UIAProviderBase (nativeHandle)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
JUCE_COMRESULT SetVisualState (WindowVisualState state) override
|
||||
{
|
||||
if (! isElementValid())
|
||||
return (HRESULT) UIA_E_ELEMENTNOTAVAILABLE;
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case WindowVisualState_Maximized:
|
||||
peer->setFullScreen (true);
|
||||
break;
|
||||
|
||||
case WindowVisualState_Minimized:
|
||||
peer->setMinimised (true);
|
||||
break;
|
||||
|
||||
case 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 (WindowVisualState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
if (peer->isFullScreen())
|
||||
*pRetVal = WindowVisualState_Maximized;
|
||||
else if (peer->isMinimised())
|
||||
*pRetVal = WindowVisualState_Minimized;
|
||||
else
|
||||
*pRetVal = WindowVisualState_Normal;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
return (HRESULT) UIA_E_NOTSUPPORTED;
|
||||
});
|
||||
}
|
||||
|
||||
JUCE_COMRESULT get_WindowInteractionState (WindowInteractionState* pRetVal) override
|
||||
{
|
||||
return withCheckedComArgs (pRetVal, *this, [&]() -> HRESULT
|
||||
{
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
*pRetVal = peer->getComponent().isCurrentlyBlockedByAnotherModalComponent()
|
||||
? WindowInteractionState::WindowInteractionState_BlockedByModalWindow
|
||||
: 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
|
158
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_WindowsUIAWrapper.h
vendored
Normal file
158
deps/juce/modules/juce_gui_basics/native/accessibility/juce_win32_WindowsUIAWrapper.h
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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, StringRef funcName)
|
||||
{
|
||||
return (FuncType) GetProcAddress (module, funcName);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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
|
621
deps/juce/modules/juce_gui_basics/native/java/app/com/rmsl/juce/ComponentPeerView.java
vendored
Normal file
621
deps/juce/modules/juce_gui_basics/native/java/app/com/rmsl/juce/ComponentPeerView.java
vendored
Normal file
@ -0,0 +1,621 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
@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 (host == 0 || ! accessibilityManager.isTouchExplorationEnabled ())
|
||||
return false;
|
||||
|
||||
int action = event.getAction ();
|
||||
long time = event.getEventTime ();
|
||||
|
||||
switch (action & MotionEvent.ACTION_MASK) // simulate "mouse" events when touch exploration is enabled
|
||||
{
|
||||
case MotionEvent.ACTION_HOVER_ENTER:
|
||||
handleMouseDown (host, event.getPointerId (0), event.getRawX (), event.getRawY (), time);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_EXIT:
|
||||
handleMouseUp (host, event.getPointerId (0), event.getRawX (), event.getRawY (), time);
|
||||
return true;
|
||||
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
handleMouseDrag (host, event.getPointerId (0), event.getRawX (), event.getRawY (), time);
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
private native void handleKeyDown (long host, int keycode, int textchar);
|
||||
private native void handleKeyUp (long host, int keycode, int textchar);
|
||||
private native void handleBackButton (long host);
|
||||
private native void handleKeyboardHidden (long host);
|
||||
|
||||
public void showKeyboard (String type)
|
||||
{
|
||||
InputMethodManager imm = (InputMethodManager) getContext ().getSystemService (Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
if (imm != null)
|
||||
{
|
||||
if (type.length () > 0)
|
||||
{
|
||||
imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
|
||||
imm.setInputMethod (getWindowToken (), type);
|
||||
keyboardDismissListener.startListening ();
|
||||
} else
|
||||
{
|
||||
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;
|
||||
|
||||
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 ());
|
||||
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 || event.getAction () != KeyEvent.ACTION_MULTIPLE)
|
||||
return super.onKeyMultiple (keyCode, count, event);
|
||||
|
||||
if (event.getCharacters () != null)
|
||||
{
|
||||
int utf8Char = event.getCharacters ().codePointAt (0);
|
||||
handleKeyDown (host, utf8Char, utf8Char);
|
||||
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 ().removeGlobalOnLayoutListener (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 is here to make keyboard entry work on a Galaxy Tab2 10.1
|
||||
@Override
|
||||
public InputConnection onCreateInputConnection (EditorInfo outAttrs)
|
||||
{
|
||||
outAttrs.actionLabel = "";
|
||||
outAttrs.hintText = "";
|
||||
outAttrs.initialCapsMode = 0;
|
||||
outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
|
||||
outAttrs.label = "";
|
||||
outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
|
||||
outAttrs.inputType = InputType.TYPE_NULL;
|
||||
|
||||
return new BaseInputConnection (this, false);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
@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 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;
|
||||
|
||||
final AccessibilityNodeInfo nodeInfo = AccessibilityNodeInfo.obtain (view, virtualViewId);
|
||||
|
||||
if (! populateAccessibilityNodeInfo (host, virtualViewId, nodeInfo))
|
||||
{
|
||||
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
deps/juce/modules/juce_gui_basics/native/java/app/com/rmsl/juce/JuceContentProviderCursor.java
vendored
Normal file
53
deps/juce/modules/juce_gui_basics/native/java/app/com/rmsl/juce/JuceContentProviderCursor.java
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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);
|
||||
}
|
45
deps/juce/modules/juce_gui_basics/native/javaopt/app/com/rmsl/juce/JuceActivity.java
vendored
Normal file
45
deps/juce/modules/juce_gui_basics/native/javaopt/app/com/rmsl/juce/JuceActivity.java
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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);
|
||||
|
||||
@Override
|
||||
protected void onNewIntent (Intent intent)
|
||||
{
|
||||
super.onNewIntent(intent);
|
||||
setIntent(intent);
|
||||
|
||||
appNewIntent (intent);
|
||||
}
|
||||
}
|
119
deps/juce/modules/juce_gui_basics/native/javaopt/app/com/rmsl/juce/JuceSharingContentProvider.java
vendored
Normal file
119
deps/juce/modules/juce_gui_basics/native/javaopt/app/com/rmsl/juce/JuceSharingContentProvider.java
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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);
|
||||
}
|
||||
}
|
||||
}
|
107
deps/juce/modules/juce_gui_basics/native/juce_MultiTouchMapper.h
vendored
Normal file
107
deps/juce/modules/juce_gui_basics/native/juce_MultiTouchMapper.h
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
54
deps/juce/modules/juce_gui_basics/native/juce_ScopedDPIAwarenessDisabler.h
vendored
Normal file
54
deps/juce/modules/juce_gui_basics/native/juce_ScopedDPIAwarenessDisabler.h
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
900
deps/juce/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp
vendored
Normal file
900
deps/juce/modules/juce_gui_basics/native/juce_android_ContentSharer.cpp
vendored
Normal file
@ -0,0 +1,900 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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:
|
||||
class Owner
|
||||
{
|
||||
public:
|
||||
virtual ~Owner() {}
|
||||
|
||||
virtual void cursorClosed (const AndroidContentSharerCursor&) = 0;
|
||||
};
|
||||
|
||||
AndroidContentSharerCursor (Owner& ownerToUse, JNIEnv* env,
|
||||
const LocalRef<jobject>& contentProvider,
|
||||
const LocalRef<jobjectArray>& resultColumns)
|
||||
: owner (ownerToUse),
|
||||
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() { return cursor.get(); }
|
||||
|
||||
void cursorClosed()
|
||||
{
|
||||
MessageManager::callAsync ([this] { owner.cursorClosed (*this); });
|
||||
}
|
||||
|
||||
void addRow (LocalRef<jobjectArray>& values)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
env->CallVoidMethod (cursor.get(), JuceContentProviderCursor.addRow, values.get());
|
||||
}
|
||||
|
||||
private:
|
||||
Owner& owner;
|
||||
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 (contentSharerCursorClosed, "contentSharerCursorClosed", "(J)V") \
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderCursor, "com/rmsl/juce/JuceContentProviderCursor", 16, javaJuceContentProviderCursor, sizeof (javaJuceContentProviderCursor))
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
static void JNICALL contentSharerCursorClosed (JNIEnv*, jobject, jlong host)
|
||||
{
|
||||
if (auto* myself = reinterpret_cast<AndroidContentSharerCursor*> (host))
|
||||
myself->cursorClosed();
|
||||
}
|
||||
};
|
||||
|
||||
AndroidContentSharerCursor::JuceContentProviderCursor_Class AndroidContentSharerCursor::JuceContentProviderCursor;
|
||||
|
||||
//==============================================================================
|
||||
class AndroidContentSharerFileObserver
|
||||
{
|
||||
public:
|
||||
class Owner
|
||||
{
|
||||
public:
|
||||
virtual ~Owner() {}
|
||||
|
||||
virtual void fileHandleClosed (const AndroidContentSharerFileObserver&) = 0;
|
||||
};
|
||||
|
||||
AndroidContentSharerFileObserver (Owner& ownerToUse, JNIEnv* env,
|
||||
const LocalRef<jobject>& contentProvider,
|
||||
const String& filepathToUse)
|
||||
: owner (ownerToUse),
|
||||
filepath (filepathToUse),
|
||||
fileObserver (GlobalRef (LocalRef<jobject> (env->NewObject (JuceContentProviderFileObserver,
|
||||
JuceContentProviderFileObserver.constructor,
|
||||
reinterpret_cast<jlong> (this),
|
||||
javaString (filepath).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>& path)
|
||||
{
|
||||
ignoreUnused (path);
|
||||
|
||||
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 ([this]
|
||||
{
|
||||
getEnv()->CallVoidMethod (fileObserver, JuceContentProviderFileObserver.stopWatching);
|
||||
owner.fileHandleClosed (*this);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int open = 32;
|
||||
static constexpr int access = 1;
|
||||
static constexpr int closeWrite = 8;
|
||||
static constexpr int closeNoWrite = 16;
|
||||
|
||||
bool fileWasRead = false;
|
||||
int numOpenedHandles = 0;
|
||||
|
||||
Owner& owner;
|
||||
String 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 (contentSharerFileObserverEvent, "contentSharerFileObserverEvent", "(JILjava/lang/String;)V") \
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceContentProviderFileObserver, "com/rmsl/juce/JuceContentProviderFileObserver", 16, javaJuceContentProviderFileObserver, sizeof (javaJuceContentProviderFileObserver))
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
static void JNICALL contentSharerFileObserverEvent (JNIEnv*, jobject /*fileObserver*/, jlong host, int event, jstring path)
|
||||
{
|
||||
if (auto* myself = reinterpret_cast<AndroidContentSharerFileObserver*> (host))
|
||||
myself->onFileEvent (event, LocalRef<jstring> (path));
|
||||
}
|
||||
};
|
||||
|
||||
AndroidContentSharerFileObserver::JuceContentProviderFileObserver_Class AndroidContentSharerFileObserver::JuceContentProviderFileObserver;
|
||||
|
||||
//==============================================================================
|
||||
class AndroidContentSharerPrepareFilesThread : private Thread
|
||||
{
|
||||
public:
|
||||
AndroidContentSharerPrepareFilesThread (AsyncUpdater& ownerToUse,
|
||||
const Array<URL>& fileUrlsToUse,
|
||||
const String& packageNameToUse,
|
||||
const String& uriBaseToUse)
|
||||
: Thread ("AndroidContentSharerPrepareFilesThread"),
|
||||
owner (ownerToUse),
|
||||
fileUrls (fileUrlsToUse),
|
||||
resultFileUris (GlobalRef (LocalRef<jobject> (getEnv()->NewObject (JavaArrayList,
|
||||
JavaArrayList.constructor,
|
||||
fileUrls.size())))),
|
||||
packageName (packageNameToUse),
|
||||
uriBase (uriBaseToUse)
|
||||
{
|
||||
startThread();
|
||||
}
|
||||
|
||||
~AndroidContentSharerPrepareFilesThread() override
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
waitForThreadToExit (10000);
|
||||
|
||||
for (auto& f : temporaryFilesFromAssetFiles)
|
||||
f.deleteFile();
|
||||
}
|
||||
|
||||
jobject getResultFileUris() { return resultFileUris.get(); }
|
||||
const StringArray& getMimeTypes() const { return mimeTypes; }
|
||||
const StringArray& getFilePaths() const { return filePaths; }
|
||||
|
||||
private:
|
||||
struct StreamCloser
|
||||
{
|
||||
StreamCloser (const LocalRef<jobject>& streamToUse)
|
||||
: stream (GlobalRef (streamToUse))
|
||||
{
|
||||
}
|
||||
|
||||
~StreamCloser()
|
||||
{
|
||||
if (stream.get() != nullptr)
|
||||
getEnv()->CallVoidMethod (stream, JavaCloseable.close);
|
||||
}
|
||||
|
||||
GlobalRef stream;
|
||||
};
|
||||
|
||||
void run() override
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
bool canSpecifyMimeTypes = true;
|
||||
|
||||
for (auto f : fileUrls)
|
||||
{
|
||||
auto scheme = f.getScheme();
|
||||
|
||||
// Only "file://" scheme or no scheme (for files in app bundle) are allowed!
|
||||
jassert (scheme.isEmpty() || scheme == "file");
|
||||
|
||||
if (scheme.isEmpty())
|
||||
{
|
||||
// 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
|
||||
f = copyAssetFileToTemporaryFile (env, f.toString (true));
|
||||
|
||||
if (f.isEmpty())
|
||||
continue;
|
||||
}
|
||||
|
||||
if (threadShouldExit())
|
||||
return;
|
||||
|
||||
auto filepath = URL::removeEscapeChars (f.toString (true).fromFirstOccurrenceOf ("file://", false, false));
|
||||
|
||||
filePaths.add (filepath);
|
||||
|
||||
auto filename = filepath.fromLastOccurrenceOf ("/", false, true);
|
||||
auto fileExtension = filename.fromLastOccurrenceOf (".", false, true);
|
||||
auto contentString = uriBase + String (filePaths.size() - 1) + "/" + filename;
|
||||
|
||||
auto uri = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidUri, AndroidUri.parse,
|
||||
javaString (contentString).get()));
|
||||
|
||||
if (canSpecifyMimeTypes)
|
||||
canSpecifyMimeTypes = fileExtension.isNotEmpty();
|
||||
|
||||
if (canSpecifyMimeTypes)
|
||||
mimeTypes.addArray (getMimeTypesForFileExtension (fileExtension));
|
||||
else
|
||||
mimeTypes.clear();
|
||||
|
||||
env->CallBooleanMethod (resultFileUris, JavaArrayList.add, uri.get());
|
||||
}
|
||||
|
||||
owner.triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
URL copyAssetFileToTemporaryFile (JNIEnv* env, const String& filename)
|
||||
{
|
||||
auto resources = LocalRef<jobject> (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 {};
|
||||
|
||||
auto assetFd = LocalRef<jobject> (env->CallObjectMethod (resources,
|
||||
AndroidResources.openRawResourceFd,
|
||||
fileId));
|
||||
|
||||
auto inputStream = StreamCloser (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);
|
||||
|
||||
auto outputStream = StreamCloser (LocalRef<jobject> (env->NewObject (JavaFileOutputStream,
|
||||
JavaFileOutputStream.constructor,
|
||||
javaString (tempFile.getFullPathName()).get())));
|
||||
|
||||
if (jniCheckHasExceptionOccurredAndClear())
|
||||
{
|
||||
// Failed to open file stream for temporary file
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto buffer = LocalRef<jbyteArray> (env->NewByteArray (1024));
|
||||
int bytesRead = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (threadShouldExit())
|
||||
return {};
|
||||
|
||||
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 {};
|
||||
}
|
||||
}
|
||||
|
||||
temporaryFilesFromAssetFiles.add (tempFile);
|
||||
|
||||
return URL (tempFile);
|
||||
}
|
||||
|
||||
AsyncUpdater& owner;
|
||||
Array<URL> fileUrls;
|
||||
|
||||
GlobalRef resultFileUris;
|
||||
String packageName;
|
||||
String uriBase;
|
||||
|
||||
StringArray filePaths;
|
||||
Array<File> temporaryFilesFromAssetFiles;
|
||||
StringArray mimeTypes;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl,
|
||||
public AndroidContentSharerFileObserver::Owner,
|
||||
public AndroidContentSharerCursor::Owner,
|
||||
public AsyncUpdater,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
ContentSharerNativeImpl (ContentSharer& cs)
|
||||
: owner (cs),
|
||||
packageName (juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageName)))),
|
||||
uriBase ("content://" + packageName + ".sharingcontentprovider/")
|
||||
{
|
||||
}
|
||||
|
||||
~ContentSharerNativeImpl() override
|
||||
{
|
||||
masterReference.clear();
|
||||
}
|
||||
|
||||
void shareFiles (const Array<URL>& files) override
|
||||
{
|
||||
if (! isContentSharingEnabled())
|
||||
{
|
||||
// You need to enable "Content Sharing" in Projucer's Android exporter.
|
||||
jassertfalse;
|
||||
owner.sharingFinished (false, {});
|
||||
}
|
||||
|
||||
prepareFilesThread.reset (new AndroidContentSharerPrepareFilesThread (*this, files, packageName, uriBase));
|
||||
}
|
||||
|
||||
void shareText (const String& text) override
|
||||
{
|
||||
if (! isContentSharingEnabled())
|
||||
{
|
||||
// You need to enable "Content Sharing" in Projucer's Android exporter.
|
||||
jassertfalse;
|
||||
owner.sharingFinished (false, {});
|
||||
}
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
auto intent = LocalRef<jobject> (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());
|
||||
|
||||
auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent, AndroidIntent.createChooser,
|
||||
intent.get(), javaString ("Choose share target").get()));
|
||||
|
||||
startAndroidActivityForResult (chooserIntent, 1003,
|
||||
[weakRef = WeakReference<ContentSharerNativeImpl> { this }] (int /*requestCode*/,
|
||||
int resultCode,
|
||||
LocalRef<jobject> /*intentData*/) mutable
|
||||
{
|
||||
if (weakRef != nullptr)
|
||||
weakRef->sharingFinished (resultCode);
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void cursorClosed (const AndroidContentSharerCursor& cursor) override
|
||||
{
|
||||
cursors.removeObject (&cursor);
|
||||
}
|
||||
|
||||
void fileHandleClosed (const AndroidContentSharerFileObserver&) override
|
||||
{
|
||||
decrementPendingFileCountAndNotifyOwnerIfReady();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
jobject openFile (const LocalRef<jobject>& contentProvider,
|
||||
const LocalRef<jobject>& uri, const LocalRef<jstring>& mode)
|
||||
{
|
||||
ignoreUnused (mode);
|
||||
|
||||
WeakReference<ContentSharerNativeImpl> weakRef (this);
|
||||
|
||||
if (weakRef == nullptr)
|
||||
return nullptr;
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
auto uriElements = getContentUriElements (env, uri);
|
||||
|
||||
if (uriElements.filepath.isEmpty())
|
||||
return nullptr;
|
||||
|
||||
return getAssetFileDescriptor (env, contentProvider, uriElements.filepath);
|
||||
}
|
||||
|
||||
jobject query (const LocalRef<jobject>& contentProvider, const LocalRef<jobject>& uri,
|
||||
const LocalRef<jobjectArray>& projection)
|
||||
{
|
||||
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();
|
||||
|
||||
auto cursor = cursors.add (new AndroidContentSharerCursor (*this, env, contentProvider,
|
||||
resultJavaColumns));
|
||||
|
||||
auto uriElements = getContentUriElements (env, uri);
|
||||
|
||||
if (uriElements.filepath.isEmpty())
|
||||
return cursor->getNativeCursor();
|
||||
|
||||
auto values = LocalRef<jobjectArray> (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")
|
||||
{
|
||||
auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
|
||||
javaString (uriElements.filepath).get()));
|
||||
|
||||
jlong fileLength = env->CallLongMethod (javaFile, JavaFile.length);
|
||||
|
||||
env->SetObjectArrayElement (values, i, env->NewObject (JavaLong,
|
||||
JavaLong.constructor,
|
||||
fileLength));
|
||||
}
|
||||
}
|
||||
|
||||
cursor->addRow (values);
|
||||
return cursor->getNativeCursor();
|
||||
}
|
||||
|
||||
jobjectArray getStreamTypes (const LocalRef<jobject>& uri, const LocalRef<jstring>& mimeTypeFilter)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
auto extension = getContentUriElements (env, uri).filename.fromLastOccurrenceOf (".", false, true);
|
||||
|
||||
if (extension.isEmpty())
|
||||
return nullptr;
|
||||
|
||||
return juceStringArrayToJava (filterMimeTypes (getMimeTypesForFileExtension (extension),
|
||||
juceString (mimeTypeFilter.get())));
|
||||
}
|
||||
|
||||
void sharingFinished (int resultCode)
|
||||
{
|
||||
sharingActivityDidFinish = true;
|
||||
|
||||
succeeded = resultCode == -1;
|
||||
|
||||
// Give content sharer a chance to request file access.
|
||||
if (nonAssetFilesPendingShare.get() == 0)
|
||||
startTimer (2000);
|
||||
else
|
||||
notifyOwnerIfReady();
|
||||
}
|
||||
|
||||
private:
|
||||
bool isContentSharingEnabled() const
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> packageManager (env->CallObjectMethod (getAppContext().get(), AndroidContext.getPackageManager));
|
||||
|
||||
constexpr int getProviders = 8;
|
||||
auto packageInfo = LocalRef<jobject> (env->CallObjectMethod (packageManager,
|
||||
AndroidPackageManager.getPackageInfo,
|
||||
javaString (packageName).get(),
|
||||
getProviders));
|
||||
auto providers = LocalRef<jobjectArray> ((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)
|
||||
{
|
||||
auto providerInfo = LocalRef<jobject> (env->GetObjectArrayElement (providers, i));
|
||||
auto authority = LocalRef<jstring> ((jstring) env->GetObjectField (providerInfo,
|
||||
AndroidProviderInfo.authority));
|
||||
|
||||
if (juceString (authority) == sharingContentProviderAuthority)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
jassert (prepareFilesThread != nullptr);
|
||||
|
||||
if (prepareFilesThread == nullptr)
|
||||
return;
|
||||
|
||||
filesPrepared (prepareFilesThread->getResultFileUris(), prepareFilesThread->getMimeTypes());
|
||||
}
|
||||
|
||||
void filesPrepared (jobject fileUris, const StringArray& mimeTypes)
|
||||
{
|
||||
auto* env = getEnv();
|
||||
|
||||
auto intent = LocalRef<jobject> (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());
|
||||
|
||||
constexpr int grantReadPermission = 1;
|
||||
env->CallObjectMethod (intent, AndroidIntent.setFlags, grantReadPermission);
|
||||
|
||||
env->CallObjectMethod (intent, AndroidIntent.putParcelableArrayListExtra,
|
||||
javaString ("android.intent.extra.STREAM").get(),
|
||||
fileUris);
|
||||
|
||||
auto chooserIntent = LocalRef<jobject> (env->CallStaticObjectMethod (AndroidIntent,
|
||||
AndroidIntent.createChooser,
|
||||
intent.get(),
|
||||
javaString ("Choose share target").get()));
|
||||
|
||||
startAndroidActivityForResult (chooserIntent, 1003,
|
||||
[weakRef = WeakReference<ContentSharerNativeImpl> { this }] (int /*requestCode*/,
|
||||
int resultCode,
|
||||
LocalRef<jobject> /*intentData*/) mutable
|
||||
{
|
||||
if (weakRef != nullptr)
|
||||
weakRef->sharingFinished (resultCode);
|
||||
});
|
||||
}
|
||||
|
||||
void decrementPendingFileCountAndNotifyOwnerIfReady()
|
||||
{
|
||||
--nonAssetFilesPendingShare;
|
||||
|
||||
notifyOwnerIfReady();
|
||||
}
|
||||
|
||||
void notifyOwnerIfReady()
|
||||
{
|
||||
if (sharingActivityDidFinish && nonAssetFilesPendingShare.get() == 0)
|
||||
owner.sharingFinished (succeeded, {});
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
notifyOwnerIfReady();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ContentUriElements
|
||||
{
|
||||
String index;
|
||||
String filename;
|
||||
String filepath;
|
||||
};
|
||||
|
||||
ContentUriElements getContentUriElements (JNIEnv* env, const LocalRef<jobject>& uri) const
|
||||
{
|
||||
jassert (prepareFilesThread != nullptr);
|
||||
|
||||
if (prepareFilesThread == nullptr)
|
||||
return {};
|
||||
|
||||
auto fullUri = juceString ((jstring) env->CallObjectMethod (uri.get(), AndroidUri.toString));
|
||||
|
||||
auto index = fullUri.fromFirstOccurrenceOf (uriBase, false, false)
|
||||
.upToFirstOccurrenceOf ("/", false, true);
|
||||
|
||||
auto filename = fullUri.fromLastOccurrenceOf ("/", false, true);
|
||||
|
||||
return { index, filename, prepareFilesThread->getFilePaths()[index.getIntValue()] };
|
||||
}
|
||||
|
||||
static StringArray getSupportedColumns()
|
||||
{
|
||||
return StringArray ("_display_name", "_size");
|
||||
}
|
||||
|
||||
jobject getAssetFileDescriptor (JNIEnv* env, const LocalRef<jobject>& contentProvider,
|
||||
const String& filepath)
|
||||
{
|
||||
// This function can be called from multiple threads.
|
||||
{
|
||||
const ScopedLock sl (nonAssetFileOpenLock);
|
||||
|
||||
if (! nonAssetFilePathsPendingShare.contains (filepath))
|
||||
{
|
||||
nonAssetFilePathsPendingShare.add (filepath);
|
||||
++nonAssetFilesPendingShare;
|
||||
|
||||
nonAssetFileObservers.add (new AndroidContentSharerFileObserver (*this, env,
|
||||
contentProvider,
|
||||
filepath));
|
||||
}
|
||||
}
|
||||
|
||||
auto javaFile = LocalRef<jobject> (env->NewObject (JavaFile, JavaFile.constructor,
|
||||
javaString (filepath).get()));
|
||||
|
||||
constexpr int modeReadOnly = 268435456;
|
||||
auto parcelFileDescriptor = LocalRef<jobject> (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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ContentSharer& owner;
|
||||
String packageName;
|
||||
String uriBase;
|
||||
|
||||
std::unique_ptr<AndroidContentSharerPrepareFilesThread> prepareFilesThread;
|
||||
|
||||
bool succeeded = false;
|
||||
String errorDescription;
|
||||
|
||||
bool sharingActivityDidFinish = false;
|
||||
|
||||
OwnedArray<AndroidContentSharerCursor> cursors;
|
||||
|
||||
Array<GlobalRef> assetFileDescriptors;
|
||||
|
||||
CriticalSection nonAssetFileOpenLock;
|
||||
StringArray nonAssetFilePathsPendingShare;
|
||||
Atomic<int> nonAssetFilesPendingShare { 0 };
|
||||
OwnedArray<AndroidContentSharerFileObserver> nonAssetFileObservers;
|
||||
|
||||
WeakReference<ContentSharerNativeImpl>::Master masterReference;
|
||||
friend class WeakReference<ContentSharerNativeImpl>;
|
||||
|
||||
//==============================================================================
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
CALLBACK (contentSharerQuery, "contentSharerQuery", "(Landroid/net/Uri;[Ljava/lang/String;)Landroid/database/Cursor;") \
|
||||
CALLBACK (contentSharerOpenFile, "contentSharerOpenFile", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;") \
|
||||
CALLBACK (contentSharerGetStreamTypes, "contentSharerGetStreamTypes", "(Landroid/net/Uri;Ljava/lang/String;)[Ljava/lang/String;") \
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceSharingContentProvider, "com/rmsl/juce/JuceSharingContentProvider", 16)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
static jobject JNICALL contentSharerQuery (JNIEnv*, jobject contentProvider, jobject uri, jobjectArray projection)
|
||||
{
|
||||
if (auto *pimpl = (ContentSharer::ContentSharerNativeImpl *) ContentSharer::getInstance ()->pimpl.get ())
|
||||
return pimpl->query (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
|
||||
LocalRef<jobject> (static_cast<jobject> (uri)),
|
||||
LocalRef<jobjectArray> (static_cast<jobjectArray> (projection)));
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static jobject JNICALL contentSharerOpenFile (JNIEnv*, jobject contentProvider, jobject uri, jstring mode)
|
||||
{
|
||||
if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get())
|
||||
return pimpl->openFile (LocalRef<jobject> (static_cast<jobject> (contentProvider)),
|
||||
LocalRef<jobject> (static_cast<jobject> (uri)),
|
||||
LocalRef<jstring> (static_cast<jstring> (mode)));
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static jobjectArray JNICALL contentSharerGetStreamTypes (JNIEnv*, jobject /*contentProvider*/, jobject uri, jstring mimeTypeFilter)
|
||||
{
|
||||
if (auto* pimpl = (ContentSharer::ContentSharerNativeImpl*) ContentSharer::getInstance()->pimpl.get())
|
||||
return pimpl->getStreamTypes (LocalRef<jobject> (static_cast<jobject> (uri)),
|
||||
LocalRef<jstring> (static_cast<jstring> (mimeTypeFilter)));
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ContentSharer::Pimpl* ContentSharer::createPimpl()
|
||||
{
|
||||
return new ContentSharerNativeImpl (*this);
|
||||
}
|
||||
|
||||
ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider_Class ContentSharer::ContentSharerNativeImpl::JuceSharingContentProvider;
|
||||
|
||||
} // namespace juce
|
240
deps/juce/modules/juce_gui_basics/native/juce_android_FileChooser.cpp
vendored
Normal file
240
deps/juce/modules/juce_gui_basics/native/juce_android_FileChooser.cpp
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 FileChooser::Native : 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);
|
||||
|
||||
// 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 (! 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();
|
||||
|
||||
Array<URL> chosenURLs;
|
||||
|
||||
if (resultCode == /*Activity.RESULT_OK*/ -1 && intentData != nullptr)
|
||||
{
|
||||
LocalRef<jobject> uri (env->CallObjectMethod (intentData.get(), AndroidIntent.getData));
|
||||
|
||||
if (uri != nullptr)
|
||||
{
|
||||
auto jStr = (jstring) env->CallObjectMethod (uri, JavaObject.toString);
|
||||
|
||||
if (jStr != nullptr)
|
||||
chosenURLs.add (URL (juceString (env, jStr)));
|
||||
}
|
||||
}
|
||||
|
||||
owner.finished (chosenURLs);
|
||||
}
|
||||
|
||||
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 (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
|
||||
}
|
||||
|
||||
} // namespace juce
|
2028
deps/juce/modules/juce_gui_basics/native/juce_android_Windowing.cpp
vendored
Normal file
2028
deps/juce/modules/juce_gui_basics/native/juce_android_Windowing.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
693
deps/juce/modules/juce_gui_basics/native/juce_common_MimeTypes.cpp
vendored
Normal file
693
deps/juce/modules/juce_gui_basics/native/juce_common_MimeTypes.cpp
vendored
Normal file
@ -0,0 +1,693 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 MimeTypeTableEntry
|
||||
{
|
||||
const char* fileExtension, *mimeType;
|
||||
|
||||
static MimeTypeTableEntry table[641];
|
||||
};
|
||||
|
||||
static StringArray getMimeTypesForFileExtension (const String& fileExtension)
|
||||
{
|
||||
StringArray result;
|
||||
|
||||
for (auto type : MimeTypeTableEntry::table)
|
||||
if (fileExtension == type.fileExtension)
|
||||
result.add (type.mimeType);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MimeTypeTableEntry MimeTypeTableEntry::table[641] =
|
||||
{
|
||||
{"3dm", "x-world/x-3dmf"},
|
||||
{"3dmf", "x-world/x-3dmf"},
|
||||
{"a", "application/octet-stream"},
|
||||
{"aab", "application/x-authorware-bin"},
|
||||
{"aam", "application/x-authorware-map"},
|
||||
{"aas", "application/x-authorware-seg"},
|
||||
{"abc", "text/vnd.abc"},
|
||||
{"acgi", "text/html"},
|
||||
{"afl", "video/animaflex"},
|
||||
{"ai", "application/postscript"},
|
||||
{"aif", "audio/aiff"},
|
||||
{"aif", "audio/x-aiff"},
|
||||
{"aifc", "audio/aiff"},
|
||||
{"aifc", "audio/x-aiff"},
|
||||
{"aiff", "audio/aiff"},
|
||||
{"aiff", "audio/x-aiff"},
|
||||
{"aim", "application/x-aim"},
|
||||
{"aip", "text/x-audiosoft-intra"},
|
||||
{"ani", "application/x-navi-animation"},
|
||||
{"aos", "application/x-nokia-9000-communicator-add-on-software"},
|
||||
{"aps", "application/mime"},
|
||||
{"arc", "application/octet-stream"},
|
||||
{"arj", "application/arj"},
|
||||
{"arj", "application/octet-stream"},
|
||||
{"art", "image/x-jg"},
|
||||
{"asf", "video/x-ms-asf"},
|
||||
{"asm", "text/x-asm"},
|
||||
{"asp", "text/asp"},
|
||||
{"asx", "application/x-mplayer2"},
|
||||
{"asx", "video/x-ms-asf"},
|
||||
{"asx", "video/x-ms-asf-plugin"},
|
||||
{"au", "audio/basic"},
|
||||
{"au", "audio/x-au"},
|
||||
{"avi", "application/x-troff-msvideo"},
|
||||
{"avi", "video/avi"},
|
||||
{"avi", "video/msvideo"},
|
||||
{"avi", "video/x-msvideo"},
|
||||
{"avs", "video/avs-video"},
|
||||
{"bcpio", "application/x-bcpio"},
|
||||
{"bin", "application/mac-binary"},
|
||||
{"bin", "application/macbinary"},
|
||||
{"bin", "application/octet-stream"},
|
||||
{"bin", "application/x-binary"},
|
||||
{"bin", "application/x-macbinary"},
|
||||
{"bm", "image/bmp"},
|
||||
{"bmp", "image/bmp"},
|
||||
{"bmp", "image/x-windows-bmp"},
|
||||
{"boo", "application/book"},
|
||||
{"book", "application/book"},
|
||||
{"boz", "application/x-bzip2"},
|
||||
{"bsh", "application/x-bsh"},
|
||||
{"bz", "application/x-bzip"},
|
||||
{"bz2", "application/x-bzip2"},
|
||||
{"c", "text/plain"},
|
||||
{"c", "text/x-c"},
|
||||
{"c++", "text/plain"},
|
||||
{"cat", "application/vnd.ms-pki.seccat"},
|
||||
{"cc", "text/plain"},
|
||||
{"cc", "text/x-c"},
|
||||
{"ccad", "application/clariscad"},
|
||||
{"cco", "application/x-cocoa"},
|
||||
{"cdf", "application/cdf"},
|
||||
{"cdf", "application/x-cdf"},
|
||||
{"cdf", "application/x-netcdf"},
|
||||
{"cer", "application/pkix-cert"},
|
||||
{"cer", "application/x-x509-ca-cert"},
|
||||
{"cha", "application/x-chat"},
|
||||
{"chat", "application/x-chat"},
|
||||
{"class", "application/java"},
|
||||
{"class", "application/java-byte-code"},
|
||||
{"class", "application/x-java-class"},
|
||||
{"com", "application/octet-stream"},
|
||||
{"com", "text/plain"},
|
||||
{"conf", "text/plain"},
|
||||
{"cpio", "application/x-cpio"},
|
||||
{"cpp", "text/x-c"},
|
||||
{"cpt", "application/mac-compactpro"},
|
||||
{"cpt", "application/x-compactpro"},
|
||||
{"cpt", "application/x-cpt"},
|
||||
{"crl", "application/pkcs-crl"},
|
||||
{"crl", "application/pkix-crl"},
|
||||
{"crt", "application/pkix-cert"},
|
||||
{"crt", "application/x-x509-ca-cert"},
|
||||
{"crt", "application/x-x509-user-cert"},
|
||||
{"csh", "application/x-csh"},
|
||||
{"csh", "text/x-script.csh"},
|
||||
{"css", "application/x-pointplus"},
|
||||
{"css", "text/css"},
|
||||
{"cxx", "text/plain"},
|
||||
{"dcr", "application/x-director"},
|
||||
{"deepv", "application/x-deepv"},
|
||||
{"def", "text/plain"},
|
||||
{"der", "application/x-x509-ca-cert"},
|
||||
{"dif", "video/x-dv"},
|
||||
{"dir", "application/x-director"},
|
||||
{"dl", "video/dl"},
|
||||
{"dl", "video/x-dl"},
|
||||
{"doc", "application/msword"},
|
||||
{"dot", "application/msword"},
|
||||
{"dp", "application/commonground"},
|
||||
{"drw", "application/drafting"},
|
||||
{"dump", "application/octet-stream"},
|
||||
{"dv", "video/x-dv"},
|
||||
{"dvi", "application/x-dvi"},
|
||||
{"dwf", "drawing/x-dwf"},
|
||||
{"dwf", "model/vnd.dwf"},
|
||||
{"dwg", "application/acad"},
|
||||
{"dwg", "image/vnd.dwg"},
|
||||
{"dwg", "image/x-dwg"},
|
||||
{"dxf", "application/dxf"},
|
||||
{"dxf", "image/vnd.dwg"},
|
||||
{"dxf", "image/x-dwg"},
|
||||
{"dxr", "application/x-director"},
|
||||
{"el", "text/x-script.elisp"},
|
||||
{"elc", "application/x-bytecode.elisp"},
|
||||
{"elc", "application/x-elc"},
|
||||
{"env", "application/x-envoy"},
|
||||
{"eps", "application/postscript"},
|
||||
{"es", "application/x-esrehber"},
|
||||
{"etx", "text/x-setext"},
|
||||
{"evy", "application/envoy"},
|
||||
{"evy", "application/x-envoy"},
|
||||
{"exe", "application/octet-stream"},
|
||||
{"f", "text/plain"},
|
||||
{"f", "text/x-fortran"},
|
||||
{"f77", "text/x-fortran"},
|
||||
{"f90", "text/plain"},
|
||||
{"f90", "text/x-fortran"},
|
||||
{"fdf", "application/vnd.fdf"},
|
||||
{"fif", "application/fractals"},
|
||||
{"fif", "image/fif"},
|
||||
{"fli", "video/fli"},
|
||||
{"fli", "video/x-fli"},
|
||||
{"flo", "image/florian"},
|
||||
{"flx", "text/vnd.fmi.flexstor"},
|
||||
{"fmf", "video/x-atomic3d-feature"},
|
||||
{"for", "text/plain"},
|
||||
{"for", "text/x-fortran"},
|
||||
{"fpx", "image/vnd.fpx"},
|
||||
{"fpx", "image/vnd.net-fpx"},
|
||||
{"frl", "application/freeloader"},
|
||||
{"funk", "audio/make"},
|
||||
{"g", "text/plain"},
|
||||
{"g3", "image/g3fax"},
|
||||
{"gif", "image/gif"},
|
||||
{"gl", "video/gl"},
|
||||
{"gl", "video/x-gl"},
|
||||
{"gsd", "audio/x-gsm"},
|
||||
{"gsm", "audio/x-gsm"},
|
||||
{"gsp", "application/x-gsp"},
|
||||
{"gss", "application/x-gss"},
|
||||
{"gtar", "application/x-gtar"},
|
||||
{"gz", "application/x-compressed"},
|
||||
{"gz", "application/x-gzip"},
|
||||
{"gzip", "application/x-gzip"},
|
||||
{"gzip", "multipart/x-gzip"},
|
||||
{"h", "text/plain"},
|
||||
{"h", "text/x-h"},
|
||||
{"hdf", "application/x-hdf"},
|
||||
{"help", "application/x-helpfile"},
|
||||
{"hgl", "application/vnd.hp-hpgl"},
|
||||
{"hh", "text/plain"},
|
||||
{"hh", "text/x-h"},
|
||||
{"hlb", "text/x-script"},
|
||||
{"hlp", "application/hlp"},
|
||||
{"hlp", "application/x-helpfile"},
|
||||
{"hlp", "application/x-winhelp"},
|
||||
{"hpg", "application/vnd.hp-hpgl"},
|
||||
{"hpgl", "application/vnd.hp-hpgl"},
|
||||
{"hqx", "application/binhex"},
|
||||
{"hqx", "application/binhex4"},
|
||||
{"hqx", "application/mac-binhex"},
|
||||
{"hqx", "application/mac-binhex40"},
|
||||
{"hqx", "application/x-binhex40"},
|
||||
{"hqx", "application/x-mac-binhex40"},
|
||||
{"hta", "application/hta"},
|
||||
{"htc", "text/x-component"},
|
||||
{"htm", "text/html"},
|
||||
{"html", "text/html"},
|
||||
{"htmls", "text/html"},
|
||||
{"htt", "text/webviewhtml"},
|
||||
{"htx", "text/html"},
|
||||
{"ice", "x-conference/x-cooltalk"},
|
||||
{"ico", "image/x-icon"},
|
||||
{"idc", "text/plain"},
|
||||
{"ief", "image/ief"},
|
||||
{"iefs", "image/ief"},
|
||||
{"iges", "application/iges"},
|
||||
{"iges", "model/iges"},
|
||||
{"igs", "application/iges"},
|
||||
{"igs", "model/iges"},
|
||||
{"ima", "application/x-ima"},
|
||||
{"imap", "application/x-httpd-imap"},
|
||||
{"inf", "application/inf"},
|
||||
{"ins", "application/x-internett-signup"},
|
||||
{"ip", "application/x-ip2"},
|
||||
{"isu", "video/x-isvideo"},
|
||||
{"it", "audio/it"},
|
||||
{"iv", "application/x-inventor"},
|
||||
{"ivr", "i-world/i-vrml"},
|
||||
{"ivy", "application/x-livescreen"},
|
||||
{"jam", "audio/x-jam"},
|
||||
{"jav", "text/plain"},
|
||||
{"jav", "text/x-java-source"},
|
||||
{"java", "text/plain"},
|
||||
{"java", "text/x-java-source"},
|
||||
{"jcm", "application/x-java-commerce"},
|
||||
{"jfif", "image/jpeg"},
|
||||
{"jfif", "image/pjpeg"},
|
||||
{"jpe", "image/jpeg"},
|
||||
{"jpe", "image/pjpeg"},
|
||||
{"jpeg", "image/jpeg"},
|
||||
{"jpeg", "image/pjpeg"},
|
||||
{"jpg", "image/jpeg"},
|
||||
{"jpg", "image/pjpeg"},
|
||||
{"jps", "image/x-jps"},
|
||||
{"js", "application/x-javascript"},
|
||||
{"jut", "image/jutvision"},
|
||||
{"kar", "audio/midi"},
|
||||
{"kar", "music/x-karaoke"},
|
||||
{"ksh", "application/x-ksh"},
|
||||
{"ksh", "text/x-script.ksh"},
|
||||
{"la", "audio/nspaudio"},
|
||||
{"la", "audio/x-nspaudio"},
|
||||
{"lam", "audio/x-liveaudio"},
|
||||
{"latex", "application/x-latex"},
|
||||
{"lha", "application/lha"},
|
||||
{"lha", "application/octet-stream"},
|
||||
{"lha", "application/x-lha"},
|
||||
{"lhx", "application/octet-stream"},
|
||||
{"list", "text/plain"},
|
||||
{"lma", "audio/nspaudio"},
|
||||
{"lma", "audio/x-nspaudio"},
|
||||
{"log", "text/plain"},
|
||||
{"lsp", "application/x-lisp"},
|
||||
{"lsp", "text/x-script.lisp"},
|
||||
{"lst", "text/plain"},
|
||||
{"lsx", "text/x-la-asf"},
|
||||
{"ltx", "application/x-latex"},
|
||||
{"lzh", "application/octet-stream"},
|
||||
{"lzh", "application/x-lzh"},
|
||||
{"lzx", "application/lzx"},
|
||||
{"lzx", "application/octet-stream"},
|
||||
{"lzx", "application/x-lzx"},
|
||||
{"m", "text/plain"},
|
||||
{"m", "text/x-m"},
|
||||
{"m1v", "video/mpeg"},
|
||||
{"m2a", "audio/mpeg"},
|
||||
{"m2v", "video/mpeg"},
|
||||
{"m3u", "audio/x-mpequrl"},
|
||||
{"man", "application/x-troff-man"},
|
||||
{"map", "application/x-navimap"},
|
||||
{"mar", "text/plain"},
|
||||
{"mbd", "application/mbedlet"},
|
||||
{"mc$", "application/x-magic-cap-package-1.0"},
|
||||
{"mcd", "application/mcad"},
|
||||
{"mcd", "application/x-mathcad"},
|
||||
{"mcf", "image/vasa"},
|
||||
{"mcf", "text/mcf"},
|
||||
{"mcp", "application/netmc"},
|
||||
{"me", "application/x-troff-me"},
|
||||
{"mht", "message/rfc822"},
|
||||
{"mhtml", "message/rfc822"},
|
||||
{"mid", "application/x-midi"},
|
||||
{"mid", "audio/midi"},
|
||||
{"mid", "audio/x-mid"},
|
||||
{"mid", "audio/x-midi"},
|
||||
{"mid", "music/crescendo"},
|
||||
{"mid", "x-music/x-midi"},
|
||||
{"midi", "application/x-midi"},
|
||||
{"midi", "audio/midi"},
|
||||
{"midi", "audio/x-mid"},
|
||||
{"midi", "audio/x-midi"},
|
||||
{"midi", "music/crescendo"},
|
||||
{"midi", "x-music/x-midi"},
|
||||
{"mif", "application/x-frame"},
|
||||
{"mif", "application/x-mif"},
|
||||
{"mime", "message/rfc822"},
|
||||
{"mime", "www/mime"},
|
||||
{"mjf", "audio/x-vnd.audioexplosion.mjuicemediafile"},
|
||||
{"mjpg", "video/x-motion-jpeg"},
|
||||
{"mm", "application/base64"},
|
||||
{"mm", "application/x-meme"},
|
||||
{"mme", "application/base64"},
|
||||
{"mod", "audio/mod"},
|
||||
{"mod", "audio/x-mod"},
|
||||
{"moov", "video/quicktime"},
|
||||
{"mov", "video/quicktime"},
|
||||
{"movie", "video/x-sgi-movie"},
|
||||
{"mp2", "audio/mpeg"},
|
||||
{"mp2", "audio/x-mpeg"},
|
||||
{"mp2", "video/mpeg"},
|
||||
{"mp2", "video/x-mpeg"},
|
||||
{"mp2", "video/x-mpeq2a"},
|
||||
{"mp3", "audio/mpeg"},
|
||||
{"mp3", "audio/mpeg3"},
|
||||
{"mp3", "audio/x-mpeg-3"},
|
||||
{"mp3", "video/mpeg"},
|
||||
{"mp3", "video/x-mpeg"},
|
||||
{"mpa", "audio/mpeg"},
|
||||
{"mpa", "video/mpeg"},
|
||||
{"mpc", "application/x-project"},
|
||||
{"mpe", "video/mpeg"},
|
||||
{"mpeg", "video/mpeg"},
|
||||
{"mpg", "audio/mpeg"},
|
||||
{"mpg", "video/mpeg"},
|
||||
{"mpga", "audio/mpeg"},
|
||||
{"mpp", "application/vnd.ms-project"},
|
||||
{"mpt", "application/x-project"},
|
||||
{"mpv", "application/x-project"},
|
||||
{"mpx", "application/x-project"},
|
||||
{"mrc", "application/marc"},
|
||||
{"ms", "application/x-troff-ms"},
|
||||
{"mv", "video/x-sgi-movie"},
|
||||
{"my", "audio/make"},
|
||||
{"mzz", "application/x-vnd.audioexplosion.mzz"},
|
||||
{"nap", "image/naplps"},
|
||||
{"naplps", "image/naplps"},
|
||||
{"nc", "application/x-netcdf"},
|
||||
{"ncm", "application/vnd.nokia.configuration-message"},
|
||||
{"nif", "image/x-niff"},
|
||||
{"niff", "image/x-niff"},
|
||||
{"nix", "application/x-mix-transfer"},
|
||||
{"nsc", "application/x-conference"},
|
||||
{"nvd", "application/x-navidoc"},
|
||||
{"o", "application/octet-stream"},
|
||||
{"oda", "application/oda"},
|
||||
{"omc", "application/x-omc"},
|
||||
{"omcd", "application/x-omcdatamaker"},
|
||||
{"omcr", "application/x-omcregerator"},
|
||||
{"p", "text/x-pascal"},
|
||||
{"p10", "application/pkcs10"},
|
||||
{"p10", "application/x-pkcs10"},
|
||||
{"p12", "application/pkcs-12"},
|
||||
{"p12", "application/x-pkcs12"},
|
||||
{"p7a", "application/x-pkcs7-signature"},
|
||||
{"p7c", "application/pkcs7-mime"},
|
||||
{"p7c", "application/x-pkcs7-mime"},
|
||||
{"p7m", "application/pkcs7-mime"},
|
||||
{"p7m", "application/x-pkcs7-mime"},
|
||||
{"p7r", "application/x-pkcs7-certreqresp"},
|
||||
{"p7s", "application/pkcs7-signature"},
|
||||
{"part", "application/pro_eng"},
|
||||
{"pas", "text/pascal"},
|
||||
{"pbm", "image/x-portable-bitmap"},
|
||||
{"pcl", "application/vnd.hp-pcl"},
|
||||
{"pcl", "application/x-pcl"},
|
||||
{"pct", "image/x-pict"},
|
||||
{"pcx", "image/x-pcx"},
|
||||
{"pdb", "chemical/x-pdb"},
|
||||
{"pdf", "application/pdf"},
|
||||
{"pfunk", "audio/make"},
|
||||
{"pfunk", "audio/make.my.funk"},
|
||||
{"pgm", "image/x-portable-graymap"},
|
||||
{"pgm", "image/x-portable-greymap"},
|
||||
{"pic", "image/pict"},
|
||||
{"pict", "image/pict"},
|
||||
{"pkg", "application/x-newton-compatible-pkg"},
|
||||
{"pko", "application/vnd.ms-pki.pko"},
|
||||
{"pl", "text/plain"},
|
||||
{"pl", "text/x-script.perl"},
|
||||
{"plx", "application/x-pixclscript"},
|
||||
{"pm", "image/x-xpixmap"},
|
||||
{"pm", "text/x-script.perl-module"},
|
||||
{"pm4", "application/x-pagemaker"},
|
||||
{"pm5", "application/x-pagemaker"},
|
||||
{"png", "image/png"},
|
||||
{"pnm", "application/x-portable-anymap"},
|
||||
{"pnm", "image/x-portable-anymap"},
|
||||
{"pot", "application/mspowerpoint"},
|
||||
{"pot", "application/vnd.ms-powerpoint"},
|
||||
{"pov", "model/x-pov"},
|
||||
{"ppa", "application/vnd.ms-powerpoint"},
|
||||
{"ppm", "image/x-portable-pixmap"},
|
||||
{"pps", "application/mspowerpoint"},
|
||||
{"pps", "application/vnd.ms-powerpoint"},
|
||||
{"ppt", "application/mspowerpoint"},
|
||||
{"ppt", "application/powerpoint"},
|
||||
{"ppt", "application/vnd.ms-powerpoint"},
|
||||
{"ppt", "application/x-mspowerpoint"},
|
||||
{"ppz", "application/mspowerpoint"},
|
||||
{"pre", "application/x-freelance"},
|
||||
{"prt", "application/pro_eng"},
|
||||
{"ps", "application/postscript"},
|
||||
{"psd", "application/octet-stream"},
|
||||
{"pvu", "paleovu/x-pv"},
|
||||
{"pwz", "application/vnd.ms-powerpoint"},
|
||||
{"py", "text/x-script.phyton"},
|
||||
{"pyc", "application/x-bytecode.python"},
|
||||
{"qcp", "audio/vnd.qcelp"},
|
||||
{"qd3", "x-world/x-3dmf"},
|
||||
{"qd3d", "x-world/x-3dmf"},
|
||||
{"qif", "image/x-quicktime"},
|
||||
{"qt", "video/quicktime"},
|
||||
{"qtc", "video/x-qtc"},
|
||||
{"qti", "image/x-quicktime"},
|
||||
{"qtif", "image/x-quicktime"},
|
||||
{"ra", "audio/x-pn-realaudio"},
|
||||
{"ra", "audio/x-pn-realaudio-plugin"},
|
||||
{"ra", "audio/x-realaudio"},
|
||||
{"ram", "audio/x-pn-realaudio"},
|
||||
{"ras", "application/x-cmu-raster"},
|
||||
{"ras", "image/cmu-raster"},
|
||||
{"ras", "image/x-cmu-raster"},
|
||||
{"rast", "image/cmu-raster"},
|
||||
{"rexx", "text/x-script.rexx"},
|
||||
{"rf", "image/vnd.rn-realflash"},
|
||||
{"rgb", "image/x-rgb"},
|
||||
{"rm", "application/vnd.rn-realmedia"},
|
||||
{"rm", "audio/x-pn-realaudio"},
|
||||
{"rmi", "audio/mid"},
|
||||
{"rmm", "audio/x-pn-realaudio"},
|
||||
{"rmp", "audio/x-pn-realaudio"},
|
||||
{"rmp", "audio/x-pn-realaudio-plugin"},
|
||||
{"rng", "application/ringing-tones"},
|
||||
{"rng", "application/vnd.nokia.ringing-tone"},
|
||||
{"rnx", "application/vnd.rn-realplayer"},
|
||||
{"roff", "application/x-troff"},
|
||||
{"rp", "image/vnd.rn-realpix"},
|
||||
{"rpm", "audio/x-pn-realaudio-plugin"},
|
||||
{"rt", "text/richtext"},
|
||||
{"rt", "text/vnd.rn-realtext"},
|
||||
{"rtf", "application/rtf"},
|
||||
{"rtf", "application/x-rtf"},
|
||||
{"rtf", "text/richtext"},
|
||||
{"rtx", "application/rtf"},
|
||||
{"rtx", "text/richtext"},
|
||||
{"rv", "video/vnd.rn-realvideo"},
|
||||
{"s", "text/x-asm"},
|
||||
{"s3m", "audio/s3m"},
|
||||
{"saveme", "application/octet-stream"},
|
||||
{"sbk", "application/x-tbook"},
|
||||
{"scm", "application/x-lotusscreencam"},
|
||||
{"scm", "text/x-script.guile"},
|
||||
{"scm", "text/x-script.scheme"},
|
||||
{"scm", "video/x-scm"},
|
||||
{"sdml", "text/plain"},
|
||||
{"sdp", "application/sdp"},
|
||||
{"sdp", "application/x-sdp"},
|
||||
{"sdr", "application/sounder"},
|
||||
{"sea", "application/sea"},
|
||||
{"sea", "application/x-sea"},
|
||||
{"set", "application/set"},
|
||||
{"sgm", "text/sgml"},
|
||||
{"sgm", "text/x-sgml"},
|
||||
{"sgml", "text/sgml"},
|
||||
{"sgml", "text/x-sgml"},
|
||||
{"sh", "application/x-bsh"},
|
||||
{"sh", "application/x-sh"},
|
||||
{"sh", "application/x-shar"},
|
||||
{"sh", "text/x-script.sh"},
|
||||
{"shar", "application/x-bsh"},
|
||||
{"shar", "application/x-shar"},
|
||||
{"shtml", "text/html"},
|
||||
{"shtml", "text/x-server-parsed-html"},
|
||||
{"sid", "audio/x-psid"},
|
||||
{"sit", "application/x-sit"},
|
||||
{"sit", "application/x-stuffit"},
|
||||
{"skd", "application/x-koan"},
|
||||
{"skm", "application/x-koan"},
|
||||
{"skp", "application/x-koan"},
|
||||
{"skt", "application/x-koan"},
|
||||
{"sl", "application/x-seelogo"},
|
||||
{"smi", "application/smil"},
|
||||
{"smil", "application/smil"},
|
||||
{"snd", "audio/basic"},
|
||||
{"snd", "audio/x-adpcm"},
|
||||
{"sol", "application/solids"},
|
||||
{"spc", "application/x-pkcs7-certificates"},
|
||||
{"spc", "text/x-speech"},
|
||||
{"spl", "application/futuresplash"},
|
||||
{"spr", "application/x-sprite"},
|
||||
{"sprite", "application/x-sprite"},
|
||||
{"src", "application/x-wais-source"},
|
||||
{"ssi", "text/x-server-parsed-html"},
|
||||
{"ssm", "application/streamingmedia"},
|
||||
{"sst", "application/vnd.ms-pki.certstore"},
|
||||
{"step", "application/step"},
|
||||
{"stl", "application/sla"},
|
||||
{"stl", "application/vnd.ms-pki.stl"},
|
||||
{"stl", "application/x-navistyle"},
|
||||
{"stp", "application/step"},
|
||||
{"sv4cpio,", "application/x-sv4cpio"},
|
||||
{"sv4crc", "application/x-sv4crc"},
|
||||
{"svf", "image/vnd.dwg"},
|
||||
{"svf", "image/x-dwg"},
|
||||
{"svr", "application/x-world"},
|
||||
{"svr", "x-world/x-svr"},
|
||||
{"swf", "application/x-shockwave-flash"},
|
||||
{"t", "application/x-troff"},
|
||||
{"talk", "text/x-speech"},
|
||||
{"tar", "application/x-tar"},
|
||||
{"tbk", "application/toolbook"},
|
||||
{"tbk", "application/x-tbook"},
|
||||
{"tcl", "application/x-tcl"},
|
||||
{"tcl", "text/x-script.tcl"},
|
||||
{"tcsh", "text/x-script.tcsh"},
|
||||
{"tex", "application/x-tex"},
|
||||
{"texi", "application/x-texinfo"},
|
||||
{"texinfo,", "application/x-texinfo"},
|
||||
{"text", "application/plain"},
|
||||
{"text", "text/plain"},
|
||||
{"tgz", "application/gnutar"},
|
||||
{"tgz", "application/x-compressed"},
|
||||
{"tif", "image/tiff"},
|
||||
{"tif", "image/x-tiff"},
|
||||
{"tiff", "image/tiff"},
|
||||
{"tiff", "image/x-tiff"},
|
||||
{"tr", "application/x-troff"},
|
||||
{"tsi", "audio/tsp-audio"},
|
||||
{"tsp", "application/dsptype"},
|
||||
{"tsp", "audio/tsplayer"},
|
||||
{"tsv", "text/tab-separated-values"},
|
||||
{"turbot", "image/florian"},
|
||||
{"txt", "text/plain"},
|
||||
{"uil", "text/x-uil"},
|
||||
{"uni", "text/uri-list"},
|
||||
{"unis", "text/uri-list"},
|
||||
{"unv", "application/i-deas"},
|
||||
{"uri", "text/uri-list"},
|
||||
{"uris", "text/uri-list"},
|
||||
{"ustar", "application/x-ustar"},
|
||||
{"ustar", "multipart/x-ustar"},
|
||||
{"uu", "application/octet-stream"},
|
||||
{"uu", "text/x-uuencode"},
|
||||
{"uue", "text/x-uuencode"},
|
||||
{"vcd", "application/x-cdlink"},
|
||||
{"vcs", "text/x-vcalendar"},
|
||||
{"vda", "application/vda"},
|
||||
{"vdo", "video/vdo"},
|
||||
{"vew", "application/groupwise"},
|
||||
{"viv", "video/vivo"},
|
||||
{"viv", "video/vnd.vivo"},
|
||||
{"vivo", "video/vivo"},
|
||||
{"vivo", "video/vnd.vivo"},
|
||||
{"vmd", "application/vocaltec-media-desc"},
|
||||
{"vmf", "application/vocaltec-media-file"},
|
||||
{"voc", "audio/voc"},
|
||||
{"voc", "audio/x-voc"},
|
||||
{"vos", "video/vosaic"},
|
||||
{"vox", "audio/voxware"},
|
||||
{"vqe", "audio/x-twinvq-plugin"},
|
||||
{"vqf", "audio/x-twinvq"},
|
||||
{"vql", "audio/x-twinvq-plugin"},
|
||||
{"vrml", "application/x-vrml"},
|
||||
{"vrml", "model/vrml"},
|
||||
{"vrml", "x-world/x-vrml"},
|
||||
{"vrt", "x-world/x-vrt"},
|
||||
{"vsd", "application/x-visio"},
|
||||
{"vst", "application/x-visio"},
|
||||
{"vsw", "application/x-visio"},
|
||||
{"w60", "application/wordperfect6.0"},
|
||||
{"w61", "application/wordperfect6.1"},
|
||||
{"w6w", "application/msword"},
|
||||
{"wav", "audio/wav"},
|
||||
{"wav", "audio/x-wav"},
|
||||
{"wb1", "application/x-qpro"},
|
||||
{"wbmp", "image/vnd.wap.wbmp"},
|
||||
{"web", "application/vnd.xara"},
|
||||
{"wiz", "application/msword"},
|
||||
{"wk1", "application/x-123"},
|
||||
{"wmf", "windows/metafile"},
|
||||
{"wml", "text/vnd.wap.wml"},
|
||||
{"wmlc", "application/vnd.wap.wmlc"},
|
||||
{"wmls", "text/vnd.wap.wmlscript"},
|
||||
{"wmlsc", "application/vnd.wap.wmlscriptc"},
|
||||
{"word", "application/msword"},
|
||||
{"wp", "application/wordperfect"},
|
||||
{"wp5", "application/wordperfect"},
|
||||
{"wp5", "application/wordperfect6.0"},
|
||||
{"wp6", "application/wordperfect"},
|
||||
{"wpd", "application/wordperfect"},
|
||||
{"wpd", "application/x-wpwin"},
|
||||
{"wq1", "application/x-lotus"},
|
||||
{"wri", "application/mswrite"},
|
||||
{"wri", "application/x-wri"},
|
||||
{"wrl", "application/x-world"},
|
||||
{"wrl", "model/vrml"},
|
||||
{"wrl", "x-world/x-vrml"},
|
||||
{"wrz", "model/vrml"},
|
||||
{"wrz", "x-world/x-vrml"},
|
||||
{"wsc", "text/scriplet"},
|
||||
{"wsrc", "application/x-wais-source"},
|
||||
{"wtk", "application/x-wintalk"},
|
||||
{"xbm", "image/x-xbitmap"},
|
||||
{"xbm", "image/x-xbm"},
|
||||
{"xbm", "image/xbm"},
|
||||
{"xdr", "video/x-amt-demorun"},
|
||||
{"xgz", "xgl/drawing"},
|
||||
{"xif", "image/vnd.xiff"},
|
||||
{"xl", "application/excel"},
|
||||
{"xla", "application/excel"},
|
||||
{"xla", "application/x-excel"},
|
||||
{"xla", "application/x-msexcel"},
|
||||
{"xlb", "application/excel"},
|
||||
{"xlb", "application/vnd.ms-excel"},
|
||||
{"xlb", "application/x-excel"},
|
||||
{"xlc", "application/excel"},
|
||||
{"xlc", "application/vnd.ms-excel"},
|
||||
{"xlc", "application/x-excel"},
|
||||
{"xld", "application/excel"},
|
||||
{"xld", "application/x-excel"},
|
||||
{"xlk", "application/excel"},
|
||||
{"xlk", "application/x-excel"},
|
||||
{"xll", "application/excel"},
|
||||
{"xll", "application/vnd.ms-excel"},
|
||||
{"xll", "application/x-excel"},
|
||||
{"xlm", "application/excel"},
|
||||
{"xlm", "application/vnd.ms-excel"},
|
||||
{"xlm", "application/x-excel"},
|
||||
{"xls", "application/excel"},
|
||||
{"xls", "application/vnd.ms-excel"},
|
||||
{"xls", "application/x-excel"},
|
||||
{"xls", "application/x-msexcel"},
|
||||
{"xlt", "application/excel"},
|
||||
{"xlt", "application/x-excel"},
|
||||
{"xlv", "application/excel"},
|
||||
{"xlv", "application/x-excel"},
|
||||
{"xlw", "application/excel"},
|
||||
{"xlw", "application/vnd.ms-excel"},
|
||||
{"xlw", "application/x-excel"},
|
||||
{"xlw", "application/x-msexcel"},
|
||||
{"xm", "audio/xm"},
|
||||
{"xml", "application/xml"},
|
||||
{"xml", "text/xml"},
|
||||
{"xmz", "xgl/movie"},
|
||||
{"xpix", "application/x-vnd.ls-xpix"},
|
||||
{"xpm", "image/x-xpixmap"},
|
||||
{"xpm", "image/xpm"},
|
||||
{"x-png", "image/png"},
|
||||
{"xsr", "video/x-amt-showrun"},
|
||||
{"xwd", "image/x-xwd"},
|
||||
{"xwd", "image/x-xwindowdump"},
|
||||
{"xyz", "chemical/x-pdb"},
|
||||
{"z", "application/x-compress"},
|
||||
{"z", "application/x-compressed"},
|
||||
{"zip", "application/x-compressed"},
|
||||
{"zip", "application/x-zip-compressed"},
|
||||
{"zip", "application/zip"},
|
||||
{"zip", "multipart/x-zip"},
|
||||
{"zoo", "application/octet-stream"}
|
||||
};
|
||||
|
||||
} // namespace juce
|
212
deps/juce/modules/juce_gui_basics/native/juce_ios_ContentSharer.cpp
vendored
Normal file
212
deps/juce/modules/juce_gui_basics/native/juce_ios_ContentSharer.cpp
vendored
Normal file
@ -0,0 +1,212 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 ContentSharer::ContentSharerNativeImpl : public ContentSharer::Pimpl,
|
||||
private Component
|
||||
{
|
||||
public:
|
||||
ContentSharerNativeImpl (ContentSharer& cs)
|
||||
: owner (cs)
|
||||
{
|
||||
static PopoverDelegateClass cls;
|
||||
popoverDelegate.reset ([cls.createInstance() init]);
|
||||
}
|
||||
|
||||
~ContentSharerNativeImpl() override
|
||||
{
|
||||
exitModalState (0);
|
||||
}
|
||||
|
||||
void shareFiles (const Array<URL>& files) override
|
||||
{
|
||||
auto urls = [NSMutableArray arrayWithCapacity: (NSUInteger) files.size()];
|
||||
|
||||
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 addObject: [NSURL fileURLWithPath: nativeFilePath]];
|
||||
}
|
||||
else {
|
||||
// just add the URL as-is (eg, NON file URLS like links, etc)
|
||||
[urls addObject: [NSURL URLWithString:juceStringToNS(f.toString(true))]];
|
||||
}
|
||||
}
|
||||
|
||||
share (urls);
|
||||
}
|
||||
|
||||
void shareText (const String& text) override
|
||||
{
|
||||
auto array = [NSArray arrayWithObject: juceStringToNS (text)];
|
||||
share (array);
|
||||
}
|
||||
|
||||
private:
|
||||
void share (NSArray* items)
|
||||
{
|
||||
if ([items count] == 0)
|
||||
{
|
||||
jassertfalse;
|
||||
owner.sharingFinished (false, "No valid items found for sharing.");
|
||||
return;
|
||||
}
|
||||
|
||||
controller.reset ([[UIActivityViewController alloc] initWithActivityItems: items
|
||||
applicationActivities: nil]);
|
||||
|
||||
controller.get().excludedActivityTypes = nil;
|
||||
|
||||
controller.get().completionWithItemsHandler = ^ (UIActivityType type, BOOL completed,
|
||||
NSArray* returnedItems, NSError* error)
|
||||
{
|
||||
ignoreUnused (type);
|
||||
ignoreUnused (returnedItems);
|
||||
|
||||
succeeded = completed;
|
||||
|
||||
if (error != nil)
|
||||
errorDescription = nsStringToJuce ([error localizedDescription]);
|
||||
|
||||
exitModalState (0);
|
||||
};
|
||||
|
||||
controller.get().modalTransitionStyle = UIModalTransitionStyleCoverVertical;
|
||||
|
||||
auto bounds = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
|
||||
setBounds (bounds);
|
||||
|
||||
setAlwaysOnTop (true);
|
||||
setVisible (true);
|
||||
addToDesktop (0);
|
||||
|
||||
enterModalState (true,
|
||||
ModalCallbackFunction::create ([this] (int)
|
||||
{
|
||||
owner.sharingFinished (succeeded, errorDescription);
|
||||
}),
|
||||
false);
|
||||
}
|
||||
|
||||
static bool isIPad()
|
||||
{
|
||||
return [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void parentHierarchyChanged() override
|
||||
{
|
||||
auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
|
||||
|
||||
if (peer != newPeer)
|
||||
{
|
||||
peer = newPeer;
|
||||
|
||||
if (isIPad())
|
||||
{
|
||||
controller.get().preferredContentSize = peer->view.frame.size;
|
||||
|
||||
auto screenBounds = [UIScreen mainScreen].bounds;
|
||||
|
||||
auto* popoverController = controller.get().popoverPresentationController;
|
||||
popoverController.sourceView = peer->view;
|
||||
popoverController.sourceRect = CGRectMake (0.f, screenBounds.size.height - 10.f, screenBounds.size.width, 10.f);
|
||||
popoverController.canOverlapSourceViewRect = YES;
|
||||
popoverController.delegate = popoverDelegate.get();
|
||||
}
|
||||
|
||||
if (auto* parentController = peer->controller)
|
||||
[parentController showViewController: controller.get() sender: parentController];
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct PopoverDelegateClass : public ObjCClass<NSObject<UIPopoverPresentationControllerDelegate>>
|
||||
{
|
||||
PopoverDelegateClass() : ObjCClass<NSObject<UIPopoverPresentationControllerDelegate>> ("PopoverDelegateClass_")
|
||||
{
|
||||
addMethod (@selector (popoverPresentationController:willRepositionPopoverToRect:inView:), willRepositionPopover, "v@:@@@");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static void willRepositionPopover (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;
|
||||
}
|
||||
};
|
||||
|
||||
ContentSharer& owner;
|
||||
UIViewComponentPeer* peer = nullptr;
|
||||
NSUniquePtr<UIActivityViewController> controller;
|
||||
NSUniquePtr<NSObject<UIPopoverPresentationControllerDelegate>> popoverDelegate;
|
||||
|
||||
bool succeeded = false;
|
||||
String errorDescription;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ContentSharer::Pimpl* ContentSharer::createPimpl()
|
||||
{
|
||||
return new ContentSharerNativeImpl (*this);
|
||||
}
|
||||
|
||||
} // namespace juce
|
387
deps/juce/modules/juce_gui_basics/native/juce_ios_FileChooser.mm
vendored
Normal file
387
deps/juce/modules/juce_gui_basics/native/juce_ios_FileChooser.mm
vendored
Normal file
@ -0,0 +1,387 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 ! (defined (__IPHONE_16_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_16_0)
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
|
||||
#define JUCE_DEPRECATION_IGNORED 1
|
||||
#endif
|
||||
|
||||
class FileChooser::Native : public FileChooser::Pimpl,
|
||||
public Component,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
Native (FileChooser& fileChooser, int flags)
|
||||
: owner (fileChooser)
|
||||
{
|
||||
static FileChooserDelegateClass delegateClass;
|
||||
delegate.reset ([delegateClass.createInstance() init]);
|
||||
FileChooserDelegateClass::setOwner (delegate.get(), this);
|
||||
|
||||
static FileChooserControllerClass controllerClass;
|
||||
auto* controllerClassInstance = controllerClass.createInstance();
|
||||
|
||||
String firstFileExtension;
|
||||
auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension));
|
||||
|
||||
if ((flags & FileBrowserComponent::saveMode) != 0)
|
||||
{
|
||||
auto currentFileOrDirectory = owner.startingFile;
|
||||
|
||||
UIDocumentPickerMode pickerMode = currentFileOrDirectory.existsAsFile()
|
||||
? UIDocumentPickerModeExportToService
|
||||
: UIDocumentPickerModeMoveToService;
|
||||
|
||||
if (! currentFileOrDirectory.existsAsFile())
|
||||
{
|
||||
auto filename = getFilename (currentFileOrDirectory, firstFileExtension);
|
||||
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 ([controllerClassInstance initWithURL: url
|
||||
inMode: pickerMode]);
|
||||
|
||||
[url release];
|
||||
}
|
||||
else
|
||||
{
|
||||
controller.reset ([controllerClassInstance initWithDocumentTypes: utTypeArray
|
||||
inMode: UIDocumentPickerModeOpen]);
|
||||
}
|
||||
|
||||
FileChooserControllerClass::setOwner (controller.get(), this);
|
||||
|
||||
[controller.get() setDelegate: delegate.get()];
|
||||
[controller.get() setModalTransitionStyle: UIModalTransitionStyleCrossDissolve];
|
||||
|
||||
setOpaque (false);
|
||||
|
||||
if (fileChooser.parent != nullptr)
|
||||
{
|
||||
[controller.get() setModalPresentationStyle: UIModalPresentationFullScreen];
|
||||
|
||||
auto chooserBounds = fileChooser.parent->getBounds();
|
||||
setBounds (chooserBounds);
|
||||
|
||||
setAlwaysOnTop (true);
|
||||
fileChooser.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);
|
||||
}
|
||||
}
|
||||
|
||||
~Native() override
|
||||
{
|
||||
exitModalState (0);
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
enterModalState (true, nullptr, true);
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
runModalLoop();
|
||||
#else
|
||||
jassertfalse;
|
||||
#endif
|
||||
}
|
||||
|
||||
void parentHierarchyChanged() override
|
||||
{
|
||||
auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer());
|
||||
|
||||
if (peer != newPeer)
|
||||
{
|
||||
peer = newPeer;
|
||||
|
||||
if (peer != nullptr)
|
||||
{
|
||||
if (auto* parentController = peer->controller)
|
||||
[parentController showViewController: controller.get() sender: parentController];
|
||||
|
||||
peer->toFront (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
pickerWasCancelled();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension)
|
||||
{
|
||||
auto filters = StringArray::fromTokens (filterWildcards, ";", "");
|
||||
StringArray result;
|
||||
|
||||
firstExtension = {};
|
||||
|
||||
if (! filters.contains ("*") && filters.size() > 0)
|
||||
{
|
||||
for (auto filter : filters)
|
||||
{
|
||||
if (filter.isEmpty())
|
||||
continue;
|
||||
|
||||
// iOS only supports file extension wild cards
|
||||
jassert (filter.upToLastOccurrenceOf (".", true, false) == "*.");
|
||||
|
||||
auto fileExtension = filter.fromLastOccurrenceOf (".", false, false);
|
||||
CFUniquePtr<CFStringRef> fileExtensionCF (fileExtension.toCFString());
|
||||
|
||||
if (firstExtension.isEmpty())
|
||||
firstExtension = fileExtension;
|
||||
|
||||
if (auto tag = CFUniquePtr<CFStringRef> (UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension, fileExtensionCF.get(), nullptr)))
|
||||
result.add (String::fromCFString (tag.get()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.add ("public.data");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void didPickDocumentAtURL (NSURL* url)
|
||||
{
|
||||
cancelPendingUpdate();
|
||||
|
||||
bool isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService
|
||||
| controller.get().documentPickerMode == UIDocumentPickerModeMoveToService;
|
||||
|
||||
NSUInteger accessOptions = isWriting ? 0 : NSFileCoordinatorReadingWithoutChanges;
|
||||
|
||||
auto* fileAccessIntent = isWriting
|
||||
? [NSFileAccessIntent writingIntentWithURL: url options: accessOptions]
|
||||
: [NSFileAccessIntent readingIntentWithURL: url options: accessOptions];
|
||||
|
||||
NSArray<NSFileAccessIntent*>* intents = @[fileAccessIntent];
|
||||
|
||||
auto fileCoordinator = [[[NSFileCoordinator alloc] initWithFilePresenter: nil] autorelease];
|
||||
|
||||
[fileCoordinator coordinateAccessWithIntents: intents queue: [NSOperationQueue mainQueue] byAccessor: ^(NSError* err)
|
||||
{
|
||||
Array<URL> chooserResults;
|
||||
|
||||
if (err == nil)
|
||||
{
|
||||
[url startAccessingSecurityScopedResource];
|
||||
|
||||
NSError* error = nil;
|
||||
|
||||
NSData* 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
|
||||
{
|
||||
auto desc = [error localizedDescription];
|
||||
ignoreUnused (desc);
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
chooserResults.add (juceUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto desc = [err localizedDescription];
|
||||
ignoreUnused (desc);
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
owner.finished (chooserResults);
|
||||
}];
|
||||
}
|
||||
|
||||
void pickerWasCancelled()
|
||||
{
|
||||
cancelPendingUpdate();
|
||||
|
||||
owner.finished ({});
|
||||
exitModalState (0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct FileChooserDelegateClass : public ObjCClass<NSObject<UIDocumentPickerDelegate>>
|
||||
{
|
||||
FileChooserDelegateClass() : ObjCClass<NSObject<UIDocumentPickerDelegate>> ("FileChooserDelegate_")
|
||||
{
|
||||
addIvar<Native*> ("owner");
|
||||
|
||||
addMethod (@selector (documentPicker:didPickDocumentAtURL:), didPickDocumentAtURL, "v@:@@");
|
||||
addMethod (@selector (documentPickerWasCancelled:), documentPickerWasCancelled, "v@:@");
|
||||
|
||||
addProtocol (@protocol (UIDocumentPickerDelegate));
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
|
||||
|
||||
//==============================================================================
|
||||
static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url)
|
||||
{
|
||||
if (auto* picker = getOwner (self))
|
||||
picker->didPickDocumentAtURL (url);
|
||||
}
|
||||
|
||||
static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*)
|
||||
{
|
||||
if (auto* picker = getOwner (self))
|
||||
picker->pickerWasCancelled();
|
||||
}
|
||||
};
|
||||
|
||||
struct FileChooserControllerClass : public ObjCClass<UIDocumentPickerViewController>
|
||||
{
|
||||
FileChooserControllerClass() : ObjCClass<UIDocumentPickerViewController> ("FileChooserController_")
|
||||
{
|
||||
addIvar<Native*> ("owner");
|
||||
addMethod (@selector (viewDidDisappear:), viewDidDisappear, "v@:@c");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); }
|
||||
static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); }
|
||||
|
||||
//==============================================================================
|
||||
static void viewDidDisappear (id self, SEL, BOOL animated)
|
||||
{
|
||||
sendSuperclassMessage<void> (self, @selector (viewDidDisappear:), animated);
|
||||
|
||||
if (auto* picker = getOwner (self))
|
||||
picker->triggerAsyncUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileChooser& owner;
|
||||
NSUniquePtr<NSObject<UIDocumentPickerDelegate>> delegate;
|
||||
NSUniquePtr<UIDocumentPickerViewController> controller;
|
||||
UIViewComponentPeer* peer = nullptr;
|
||||
|
||||
static FileChooserDelegateClass fileChooserDelegateClass;
|
||||
static FileChooserControllerClass fileChooserControllerClass;
|
||||
|
||||
//==============================================================================
|
||||
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 std::make_shared<FileChooser::Native> (owner, flags);
|
||||
}
|
||||
|
||||
#if JUCE_DEPRECATION_IGNORED
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
#endif
|
||||
|
||||
#undef JUCE_DEPRECATION_IGNORED
|
||||
|
||||
} // namespace juce
|
1273
deps/juce/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm
vendored
Normal file
1273
deps/juce/modules/juce_gui_basics/native/juce_ios_UIViewComponentPeer.mm
vendored
Normal file
File diff suppressed because it is too large
Load Diff
891
deps/juce/modules/juce_gui_basics/native/juce_ios_Windowing.mm
vendored
Normal file
891
deps/juce/modules/juce_gui_basics/native/juce_ios_Windowing.mm
vendored
Normal file
@ -0,0 +1,891 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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;
|
||||
}
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS && defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
@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 didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings;
|
||||
- (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;
|
||||
- (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;
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
- (void) userNotificationCenter: (UNUserNotificationCenter*) center willPresentNotification: (UNNotification*) notification
|
||||
withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler;
|
||||
- (void) userNotificationCenter: (UNUserNotificationCenter*) center didReceiveNotificationResponse: (UNNotificationResponse*) response
|
||||
withCompletionHandler: (void(^)())completionHandler;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@implementation JuceAppStartupDelegate
|
||||
|
||||
NSObject* _pushNotificationsDelegate;
|
||||
|
||||
- (id) init
|
||||
{
|
||||
self = [super init];
|
||||
appSuspendTask = UIBackgroundTaskInvalid;
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS && defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
[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;
|
||||
}
|
||||
|
||||
- (BOOL) application:(UIApplication *)application openURL:(NSURL *)
|
||||
url sourceApplication:(NSString *)sourceApplication annotation:(id)
|
||||
annotation
|
||||
{
|
||||
if(!JUCEApplicationBase::getInstance())
|
||||
{
|
||||
[self applicationDidFinishLaunching:application];
|
||||
}
|
||||
|
||||
// mostly stolen from didPickDocumentAtURL
|
||||
NSUInteger accessOptions = NSFileCoordinatorReadingWithoutChanges;
|
||||
|
||||
auto *fileAccessIntent = [NSFileAccessIntent readingIntentWithURL:url options:accessOptions];
|
||||
|
||||
NSArray<NSFileAccessIntent *> *intents = @[fileAccessIntent];
|
||||
|
||||
auto *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
|
||||
|
||||
[fileCoordinator coordinateAccessWithIntents:intents queue:[NSOperationQueue mainQueue] byAccessor:^(NSError *err) {
|
||||
if (err == nil) {
|
||||
[url startAccessingSecurityScopedResource];
|
||||
|
||||
NSError *error = nil;
|
||||
|
||||
NSData *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 {
|
||||
auto *desc = [error localizedDescription];
|
||||
ignoreUnused(desc);
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
if (auto *app = JUCEApplicationBase::getInstance())
|
||||
{
|
||||
app->urlOpened(juceUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
} else {
|
||||
auto *desc = [err localizedDescription];
|
||||
ignoreUnused(desc);
|
||||
jassertfalse;
|
||||
}
|
||||
}];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#if JUCE_PUSH_NOTIFICATIONS
|
||||
- (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 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];
|
||||
}
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
}
|
||||
|
||||
#if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
|
||||
- (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
|
||||
#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
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class iOSMessageBox
|
||||
{
|
||||
public:
|
||||
iOSMessageBox (const MessageBoxOptions& opts, std::unique_ptr<ModalComponentManager::Callback>&& cb)
|
||||
: callback (std::move (cb))
|
||||
{
|
||||
if (currentlyFocusedPeer != nullptr)
|
||||
{
|
||||
UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (opts.getTitle())
|
||||
message: juceStringToNS (opts.getMessage())
|
||||
preferredStyle: UIAlertControllerStyleAlert];
|
||||
|
||||
addButton (alert, opts.getButtonText (0));
|
||||
addButton (alert, opts.getButtonText (1));
|
||||
addButton (alert, opts.getButtonText (2));
|
||||
|
||||
[currentlyFocusedPeer->controller presentViewController: alert
|
||||
animated: YES
|
||||
completion: nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
int getResult()
|
||||
{
|
||||
jassert (callback == nullptr);
|
||||
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
while (result < 0)
|
||||
[[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void buttonClicked (int buttonIndex) noexcept
|
||||
{
|
||||
result = buttonIndex;
|
||||
|
||||
if (callback != nullptr)
|
||||
{
|
||||
callback->modalStateFinished (result);
|
||||
delete this;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void addButton (UIAlertController* alert, const String& text)
|
||||
{
|
||||
if (! text.isEmpty())
|
||||
{
|
||||
const auto index = [[alert actions] count];
|
||||
|
||||
[alert addAction: [UIAlertAction actionWithTitle: juceStringToNS (text)
|
||||
style: UIAlertActionStyleDefault
|
||||
handler: ^(UIAlertAction*) { this->buttonClicked ((int) index); }]];
|
||||
}
|
||||
}
|
||||
|
||||
int result = -1;
|
||||
std::unique_ptr<ModalComponentManager::Callback> callback;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
static int showDialog (const MessageBoxOptions& options,
|
||||
ModalComponentManager::Callback* callbackIn,
|
||||
AlertWindowMappings::MapFn mapFn)
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
if (callbackIn == nullptr)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
jassert (mapFn != nullptr);
|
||||
|
||||
iOSMessageBox messageBox (options, nullptr);
|
||||
return mapFn (messageBox.getResult());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
new iOSMessageBox (options, AlertWindowMappings::getWrappedCallback (callbackIn, mapFn));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType /*iconType*/,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/)
|
||||
{
|
||||
showDialog (MessageBoxOptions()
|
||||
.withTitle (title)
|
||||
.withMessage (message)
|
||||
.withButton (TRANS("OK")),
|
||||
nullptr, AlertWindowMappings::messageBox);
|
||||
}
|
||||
|
||||
int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
|
||||
{
|
||||
return showDialog (options, nullptr, AlertWindowMappings::noMapping);
|
||||
}
|
||||
#endif
|
||||
|
||||
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType /*iconType*/,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
showDialog (MessageBoxOptions()
|
||||
.withTitle (title)
|
||||
.withMessage (message)
|
||||
.withButton (TRANS("OK")),
|
||||
callback, AlertWindowMappings::messageBox);
|
||||
}
|
||||
|
||||
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType /*iconType*/,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
return showDialog (MessageBoxOptions()
|
||||
.withTitle (title)
|
||||
.withMessage (message)
|
||||
.withButton (TRANS("OK"))
|
||||
.withButton (TRANS("Cancel")),
|
||||
callback, AlertWindowMappings::okCancel) != 0;
|
||||
}
|
||||
|
||||
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType /*iconType*/,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
return showDialog (MessageBoxOptions()
|
||||
.withTitle (title)
|
||||
.withMessage (message)
|
||||
.withButton (TRANS("Yes"))
|
||||
.withButton (TRANS("No"))
|
||||
.withButton (TRANS("Cancel")),
|
||||
callback, AlertWindowMappings::yesNoCancel);
|
||||
}
|
||||
|
||||
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType /*iconType*/,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
return showDialog (MessageBoxOptions()
|
||||
.withTitle (title)
|
||||
.withMessage (message)
|
||||
.withButton (TRANS("Yes"))
|
||||
.withButton (TRANS("No")),
|
||||
callback, AlertWindowMappings::okCancel);
|
||||
}
|
||||
|
||||
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
showDialog (options, callback, AlertWindowMappings::noMapping);
|
||||
}
|
||||
|
||||
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
|
||||
std::function<void (int)> callback)
|
||||
{
|
||||
showAsync (options, ModalCallbackFunction::create (callback));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool juce_areThereAnyAlwaysOnTopWindows()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Image juce_createIconForFile (const File&)
|
||||
{
|
||||
return Image();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SystemClipboard::copyTextToClipboard (const String& text)
|
||||
{
|
||||
[[UIPasteboard generalPasteboard] setValue: juceStringToNS (text)
|
||||
forPasteboardType: @"public.text"];
|
||||
}
|
||||
|
||||
String SystemClipboard::getTextFromClipboard()
|
||||
{
|
||||
id value = [[UIPasteboard generalPasteboard] valueForPasteboardType: @"public.text"];
|
||||
if ([value isKindOfClass:[NSString class]]) {
|
||||
return nsStringToJuce (value);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool MouseInputSource::SourceList::addSource()
|
||||
{
|
||||
addSource (sources.size(), MouseInputSource::InputSourceType::touch);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MouseInputSource::SourceList::canUseTouch()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Desktop::canUseSemiTransparentWindows() noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Desktop::isDarkModeActive() const
|
||||
{
|
||||
#if defined (__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0
|
||||
if (@available (iOS 12.0, *))
|
||||
return [[[UIScreen mainScreen] traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class Desktop::NativeDarkModeChangeDetectorImpl
|
||||
{
|
||||
public:
|
||||
NativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
static DelegateClass delegateClass;
|
||||
|
||||
delegate = [delegateClass.createInstance() init];
|
||||
object_setInstanceVariable (delegate, "owner", this);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[[NSNotificationCenter defaultCenter] addObserver: delegate
|
||||
selector: @selector (darkModeChanged:)
|
||||
name: UIViewComponentPeer::getDarkModeNotificationName()
|
||||
object: nil];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
~NativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
object_setInstanceVariable (delegate, "owner", nullptr);
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: delegate];
|
||||
[delegate release];
|
||||
}
|
||||
|
||||
void darkModeChanged()
|
||||
{
|
||||
Desktop::getInstance().darkModeChanged();
|
||||
}
|
||||
|
||||
private:
|
||||
struct DelegateClass : public ObjCClass<NSObject>
|
||||
{
|
||||
DelegateClass() : ObjCClass<NSObject> ("JUCEDelegate_")
|
||||
{
|
||||
addIvar<NativeDarkModeChangeDetectorImpl*> ("owner");
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (darkModeChanged:), darkModeChanged, "v@:@");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void darkModeChanged (id self, SEL, NSNotification*)
|
||||
{
|
||||
if (auto* owner = getIvar<NativeDarkModeChangeDetectorImpl*> (self, "owner"))
|
||||
owner->darkModeChanged();
|
||||
}
|
||||
};
|
||||
|
||||
id delegate = nil;
|
||||
|
||||
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 defined (__IPHONE_11_0)
|
||||
if (@available (iOS 11.0, *))
|
||||
{
|
||||
UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets;
|
||||
|
||||
auto getInset = [&] (CGFloat original) { return roundToInt (original / masterScale); };
|
||||
|
||||
return { getInset (safeInsets.top), getInset (safeInsets.left),
|
||||
getInset (safeInsets.bottom), getInset (safeInsets.right) };
|
||||
}
|
||||
#endif
|
||||
|
||||
auto statusBarSize = [UIApplication sharedApplication].statusBarFrame.size;
|
||||
auto statusBarHeight = jmin (statusBarSize.width, statusBarSize.height);
|
||||
|
||||
return { roundToInt (statusBarHeight / masterScale), 0, 0, 0 };
|
||||
}
|
||||
|
||||
void Displays::findDisplays (float masterScale)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
UIScreen* s = [UIScreen mainScreen];
|
||||
|
||||
Display d;
|
||||
d.totalArea = convertToRectInt ([s bounds]) / masterScale;
|
||||
d.userArea = getRecommendedWindowBounds() / masterScale;
|
||||
d.safeAreaInsets = getSafeAreaInsets (masterScale);
|
||||
d.isMain = true;
|
||||
d.scale = masterScale * s.scale;
|
||||
d.dpi = 160 * d.scale;
|
||||
|
||||
displays.add (d);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
274
deps/juce/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp
vendored
Normal file
274
deps/juce/modules/juce_gui_basics/native/juce_linux_FileChooser.cpp
vendored
Normal file
@ -0,0 +1,274 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 : 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");
|
||||
|
||||
if (warnAboutOverwrite)
|
||||
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
|
870
deps/juce/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
vendored
Normal file
870
deps/juce/modules/juce_gui_basics/native/juce_linux_Windowing.cpp
vendored
Normal file
@ -0,0 +1,870 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 int numAlwaysOnTopPeers = 0;
|
||||
bool juce_areThereAnyAlwaysOnTopWindows() { return numAlwaysOnTopPeers > 0; }
|
||||
|
||||
//==============================================================================
|
||||
class LinuxComponentPeer : 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)
|
||||
++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(); };
|
||||
}
|
||||
|
||||
~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)
|
||||
--numAlwaysOnTopPeers;
|
||||
}
|
||||
|
||||
::Window getWindowHandle() const noexcept
|
||||
{
|
||||
return windowH;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void* getNativeHandle() const override
|
||||
{
|
||||
return reinterpret_cast<void*> (getWindowHandle());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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)
|
||||
return;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
BorderSize<int> getFrameSize() const override
|
||||
{
|
||||
return windowBorder;
|
||||
}
|
||||
|
||||
Point<float> localToGlobal (Point<float> relativePosition) override
|
||||
{
|
||||
return relativePosition + getScreenPosition (false).toFloat();
|
||||
}
|
||||
|
||||
Point<float> globalToLocal (Point<float> screenPosition) override
|
||||
{
|
||||
return screenPosition - getScreenPosition (false).toFloat();
|
||||
}
|
||||
|
||||
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 (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;
|
||||
|
||||
if (auto* peer = c->getPeer())
|
||||
if (peer->contains (localPos + bounds.getPosition() - peer->getBounds().getPosition(), 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; }
|
||||
|
||||
//==============================================================================
|
||||
void updateWindowBounds()
|
||||
{
|
||||
jassert (windowH != 0);
|
||||
if (windowH != 0)
|
||||
{
|
||||
auto physicalBounds = XWindowSystem::getInstance()->getWindowBounds (windowH, parentWindow);
|
||||
|
||||
updateScaleFactorFromNewBounds (physicalBounds, true);
|
||||
|
||||
bounds = parentWindow == 0 ? Desktop::getInstance().getDisplays().physicalToLogical (physicalBounds)
|
||||
: physicalBounds / currentScaleFactor;
|
||||
}
|
||||
}
|
||||
|
||||
void updateBorderSize()
|
||||
{
|
||||
if ((styleFlags & windowHasTitleBar) == 0)
|
||||
windowBorder = {};
|
||||
else if (windowBorder.getTopAndBottom() == 0 && windowBorder.getLeftAndRight() == 0)
|
||||
windowBorder = XWindowSystem::getInstance()->getBorderSize (windowH);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static bool isActiveApplication;
|
||||
bool focused = false;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class LinuxRepaintManager : public Timer
|
||||
{
|
||||
public:
|
||||
LinuxRepaintManager (LinuxComponentPeer& p)
|
||||
: peer (p),
|
||||
isSemiTransparentWindow ((peer.getStyleFlags() & ComponentPeer::windowIsSemiTransparent) != 0)
|
||||
{
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
XWindowSystem::getInstance()->processPendingPaintsForWindow (peer.windowH);
|
||||
|
||||
if (XWindowSystem::getInstance()->getNumPaintsPendingForWindow (peer.windowH) > 0)
|
||||
return;
|
||||
|
||||
if (! regionsNeedingRepaint.isEmpty())
|
||||
{
|
||||
stopTimer();
|
||||
performAnyPendingRepaintsNow();
|
||||
}
|
||||
else if (Time::getApproximateMillisecondCounter() > lastTimeImageUsed + 3000)
|
||||
{
|
||||
stopTimer();
|
||||
image = Image();
|
||||
}
|
||||
}
|
||||
|
||||
void repaint (Rectangle<int> area)
|
||||
{
|
||||
if (! isTimerRunning())
|
||||
startTimer (repaintTimerPeriod);
|
||||
|
||||
regionsNeedingRepaint.add (area * peer.currentScaleFactor);
|
||||
}
|
||||
|
||||
void performAnyPendingRepaintsNow()
|
||||
{
|
||||
if (XWindowSystem::getInstance()->getNumPaintsPendingForWindow (peer.windowH) > 0)
|
||||
{
|
||||
startTimer (repaintTimerPeriod);
|
||||
return;
|
||||
}
|
||||
|
||||
auto originalRepaintRegion = regionsNeedingRepaint;
|
||||
regionsNeedingRepaint.clear();
|
||||
auto totalArea = originalRepaintRegion.getBounds();
|
||||
|
||||
if (! totalArea.isEmpty())
|
||||
{
|
||||
if (image.isNull() || image.getWidth() < totalArea.getWidth()
|
||||
|| image.getHeight() < totalArea.getHeight())
|
||||
{
|
||||
image = XWindowSystem::getInstance()->createImage (isSemiTransparentWindow,
|
||||
totalArea.getWidth(), totalArea.getHeight(),
|
||||
useARGBImagesForRendering);
|
||||
}
|
||||
|
||||
startTimer (repaintTimerPeriod);
|
||||
|
||||
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();
|
||||
startTimer (repaintTimerPeriod);
|
||||
}
|
||||
|
||||
private:
|
||||
enum { repaintTimerPeriod = 1000 / 100 };
|
||||
|
||||
LinuxComponentPeer& peer;
|
||||
const bool isSemiTransparentWindow;
|
||||
Image image;
|
||||
uint32 lastTimeImageUsed = 0;
|
||||
RectangleList<int> regionsNeedingRepaint;
|
||||
|
||||
bool useARGBImagesForRendering = XWindowSystem::getInstance()->canUseARGBImages();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (LinuxRepaintManager)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
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); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<LinuxRepaintManager> repainter;
|
||||
|
||||
::Window windowH = {}, parentWindow = {};
|
||||
Rectangle<int> bounds;
|
||||
BorderSize<int> windowBorder;
|
||||
bool fullScreen = false, isAlwaysOnTop = false;
|
||||
double currentScaleFactor = 1.0;
|
||||
Array<Component*> glRepaintListeners;
|
||||
|
||||
//==============================================================================
|
||||
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 MouseInputSource::SourceList::addSource()
|
||||
{
|
||||
if (sources.isEmpty())
|
||||
{
|
||||
addSource (0, MouseInputSource::InputSourceType::mouse);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MouseInputSource::SourceList::canUseTouch()
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void* CustomMouseCursorInfo::create() const
|
||||
{
|
||||
return XWindowSystem::getInstance()->createCustomMouseCursorInfo (image, hotspot);
|
||||
}
|
||||
|
||||
void MouseCursor::deleteMouseCursor (void* cursorHandle, bool)
|
||||
{
|
||||
if (cursorHandle != nullptr)
|
||||
XWindowSystem::getInstance()->deleteMouseCursor (cursorHandle);
|
||||
}
|
||||
|
||||
void* MouseCursor::createStandardMouseCursor (MouseCursor::StandardCursorType type)
|
||||
{
|
||||
return XWindowSystem::getInstance()->createStandardMouseCursor (type);
|
||||
}
|
||||
|
||||
void MouseCursor::showInWindow (ComponentPeer* peer) const
|
||||
{
|
||||
if (peer != nullptr)
|
||||
XWindowSystem::getInstance()->showCursor ((::Window) peer->getNativeHandle(), getHandle());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static int showDialog (const MessageBoxOptions& options,
|
||||
ModalComponentManager::Callback* callback,
|
||||
Async async)
|
||||
{
|
||||
const auto dummyCallback = [] (int) {};
|
||||
|
||||
switch (options.getNumButtons())
|
||||
{
|
||||
case 2:
|
||||
{
|
||||
if (async == Async::yes && callback == nullptr)
|
||||
callback = ModalCallbackFunction::create (dummyCallback);
|
||||
|
||||
return AlertWindow::showOkCancelBox (options.getIconType(),
|
||||
options.getTitle(),
|
||||
options.getMessage(),
|
||||
options.getButtonText (0),
|
||||
options.getButtonText (1),
|
||||
options.getAssociatedComponent(),
|
||||
callback) ? 1 : 0;
|
||||
}
|
||||
|
||||
case 3:
|
||||
{
|
||||
if (async == Async::yes && callback == nullptr)
|
||||
callback = ModalCallbackFunction::create (dummyCallback);
|
||||
|
||||
return AlertWindow::showYesNoCancelBox (options.getIconType(),
|
||||
options.getTitle(),
|
||||
options.getMessage(),
|
||||
options.getButtonText (0),
|
||||
options.getButtonText (1),
|
||||
options.getButtonText (2),
|
||||
options.getAssociatedComponent(),
|
||||
callback);
|
||||
}
|
||||
|
||||
case 1:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
if (async == Async::no)
|
||||
{
|
||||
AlertWindow::showMessageBox (options.getIconType(),
|
||||
options.getTitle(),
|
||||
options.getMessage(),
|
||||
options.getButtonText (0),
|
||||
options.getAssociatedComponent());
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (options.getIconType(),
|
||||
options.getTitle(),
|
||||
options.getMessage(),
|
||||
options.getButtonText (0),
|
||||
options.getAssociatedComponent(),
|
||||
callback);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/)
|
||||
{
|
||||
AlertWindow::showMessageBox (iconType, title, message);
|
||||
}
|
||||
|
||||
int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
|
||||
{
|
||||
return showDialog (options, nullptr, Async::no);
|
||||
}
|
||||
#endif
|
||||
|
||||
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* associatedComponent,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
AlertWindow::showMessageBoxAsync (iconType, title, message, {}, associatedComponent, callback);
|
||||
}
|
||||
|
||||
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* associatedComponent,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
return AlertWindow::showOkCancelBox (iconType, title, message, {}, {}, associatedComponent, callback);
|
||||
}
|
||||
|
||||
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* associatedComponent,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
return AlertWindow::showYesNoCancelBox (iconType, title, message, {}, {}, {},
|
||||
associatedComponent, callback);
|
||||
}
|
||||
|
||||
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* associatedComponent,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
return AlertWindow::showOkCancelBox (iconType, title, message, TRANS("Yes"), TRANS("No"),
|
||||
associatedComponent, callback);
|
||||
}
|
||||
|
||||
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
showDialog (options, callback, Async::yes);
|
||||
}
|
||||
|
||||
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
|
||||
std::function<void (int)> callback)
|
||||
{
|
||||
showAsync (options, ModalCallbackFunction::create (callback));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Image juce_createIconForFile (const File&)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void juce_LinuxAddRepaintListener (ComponentPeer* peer, Component* dummy)
|
||||
{
|
||||
if (auto* linuxPeer = dynamic_cast<LinuxComponentPeer*> (peer))
|
||||
linuxPeer->addOpenGLRepaintListener (dummy);
|
||||
}
|
||||
|
||||
void juce_LinuxRemoveRepaintListener (ComponentPeer* peer, Component* dummy)
|
||||
{
|
||||
if (auto* linuxPeer = dynamic_cast<LinuxComponentPeer*> (peer))
|
||||
linuxPeer->removeOpenGLRepaintListener (dummy);
|
||||
}
|
||||
|
||||
} // namespace juce
|
403
deps/juce/modules/juce_gui_basics/native/juce_mac_FileChooser.mm
vendored
Normal file
403
deps/juce/modules/juce_gui_basics/native/juce_mac_FileChooser.mm
vendored
Normal file
@ -0,0 +1,403 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 : 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);
|
||||
|
||||
static DelegateClass delegateClass;
|
||||
static SafeSavePanel safeSavePanel;
|
||||
static SafeOpenPanel safeOpenPanel;
|
||||
|
||||
panel = isSave ? [safeSavePanel.createInstance() init]
|
||||
: [safeOpenPanel.createInstance() init];
|
||||
|
||||
delegate = [delegateClass.createInstance() init];
|
||||
object_setInstanceVariable (delegate, "cppObject", this);
|
||||
|
||||
[panel setDelegate: delegate];
|
||||
|
||||
filters.addTokens (owner.filters.replaceCharacters (",:", ";;"), ";", String());
|
||||
filters.trim();
|
||||
filters.removeEmptyStrings();
|
||||
|
||||
NSString* nsTitle = juceStringToNS (owner.title);
|
||||
[panel setTitle: nsTitle];
|
||||
|
||||
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 (! 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];
|
||||
|
||||
nsViewPreview = nil;
|
||||
preview = nullptr;
|
||||
}
|
||||
|
||||
[panel close];
|
||||
[panel release];
|
||||
}
|
||||
|
||||
if (delegate != nil)
|
||||
{
|
||||
[delegate release];
|
||||
delegate = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
if (panel != nil)
|
||||
{
|
||||
setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows());
|
||||
addToDesktop (0);
|
||||
|
||||
enterModalState (true);
|
||||
[panel beginWithCompletionHandler:CreateObjCBlock (this, &Native::finished)];
|
||||
|
||||
if (preview != nullptr)
|
||||
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;
|
||||
|
||||
void finished (NSInteger result)
|
||||
{
|
||||
Array<URL> chooserResults;
|
||||
|
||||
exitModalState (0);
|
||||
|
||||
const auto okResult = []() -> NSInteger
|
||||
{
|
||||
if (@available (macOS 10.9, *))
|
||||
return NSModalResponseOK;
|
||||
|
||||
return NSFileHandlingPanelOKButton;
|
||||
}();
|
||||
|
||||
if (panel != nil && result == okResult)
|
||||
{
|
||||
auto addURLResult = [&chooserResults] (NSURL* urlToAdd)
|
||||
{
|
||||
auto scheme = nsStringToJuce ([urlToAdd scheme]);
|
||||
auto pathComponents = StringArray::fromTokens (nsStringToJuce ([urlToAdd path]), "/", {});
|
||||
|
||||
for (auto& component : pathComponents)
|
||||
component = URL::addEscapeChars (component, false);
|
||||
|
||||
chooserResults.add (URL (scheme + "://" + pathComponents.joinIntoString ("/")));
|
||||
};
|
||||
|
||||
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 shouldShowFilename (const String& filenameToTest)
|
||||
{
|
||||
const File f (filenameToTest);
|
||||
auto nsFilename = juceStringToNS (filenameToTest);
|
||||
|
||||
for (int i = filters.size(); --i >= 0;)
|
||||
if (f.getFileName().matchesWildcard (filters[i], true))
|
||||
return true;
|
||||
|
||||
return f.isDirectory()
|
||||
&& ! [[NSWorkspace sharedWorkspace] isFilePackageAtPath: nsFilename];
|
||||
}
|
||||
|
||||
void panelSelectionDidChange (id sender)
|
||||
{
|
||||
jassert (sender == panel);
|
||||
ignoreUnused (sender);
|
||||
|
||||
// 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() { return YES; }
|
||||
|
||||
template <typename Base>
|
||||
struct SafeModalPanel : public ObjCClass<Base>
|
||||
{
|
||||
explicit SafeModalPanel (const char* name) : ObjCClass<Base> (name)
|
||||
{
|
||||
this->addMethod (@selector (preventsApplicationTerminationWhenModal),
|
||||
preventsApplicationTerminationWhenModal,
|
||||
"c@:");
|
||||
|
||||
this->registerClass();
|
||||
}
|
||||
};
|
||||
|
||||
struct SafeSavePanel : SafeModalPanel<NSSavePanel>
|
||||
{
|
||||
SafeSavePanel() : SafeModalPanel ("SaveSavePanel_") {}
|
||||
};
|
||||
|
||||
struct SafeOpenPanel : SafeModalPanel<NSOpenPanel>
|
||||
{
|
||||
SafeOpenPanel() : SafeModalPanel ("SaveOpenPanel_") {}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct DelegateClass : public ObjCClass<DelegateType>
|
||||
{
|
||||
DelegateClass() : ObjCClass<DelegateType> ("JUCEFileChooser_")
|
||||
{
|
||||
addIvar<Native*> ("cppObject");
|
||||
|
||||
addMethod (@selector (panel:shouldShowFilename:), shouldShowFilename, "c@:@@");
|
||||
addMethod (@selector (panelSelectionDidChange:), panelSelectionDidChange, "c@");
|
||||
|
||||
addProtocol (@protocol (NSOpenSavePanelDelegate));
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static BOOL shouldShowFilename (id self, SEL, id /*sender*/, NSString* filename)
|
||||
{
|
||||
auto* _this = getIvar<Native*> (self, "cppObject");
|
||||
|
||||
return _this->shouldShowFilename (nsStringToJuce (filename)) ? YES : NO;
|
||||
}
|
||||
|
||||
static void panelSelectionDidChange (id self, SEL, id sender)
|
||||
{
|
||||
auto* _this = getIvar<Native*> (self, "cppObject");
|
||||
|
||||
_this->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
|
||||
}
|
||||
|
||||
}
|
839
deps/juce/modules/juce_gui_basics/native/juce_mac_MainMenu.mm
vendored
Normal file
839
deps/juce/modules/juce_gui_basics/native/juce_mac_MainMenu.mm
vendored
Normal file
@ -0,0 +1,839 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 JuceMainMenuBarHolder : 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 : 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
|
||||
{
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
auto item = [[NSMenuItem alloc] initWithTitle: text
|
||||
action: @selector (menuItemInvoked:)
|
||||
keyEquivalent: nsEmptyString()];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
[item setTag: topLevelIndex];
|
||||
[item setEnabled: i.isEnabled];
|
||||
#if defined (MAC_OS_X_VERSION_10_13) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_13
|
||||
[item setState: i.isTicked ? NSControlStateValueOn : NSControlStateValueOff];
|
||||
#else
|
||||
[item setState: i.isTicked ? NSOnState : NSOffState];
|
||||
#endif
|
||||
[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: nil
|
||||
keyEquivalent: f35String];
|
||||
[item setTarget: nil];
|
||||
[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 it's 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]))
|
||||
{
|
||||
auto menuItem = [parentMenu itemAtIndex:menuItemIndex];
|
||||
|
||||
if (auto submenu = [menuItem submenu])
|
||||
removeItemRecursive (submenu);
|
||||
|
||||
[parentMenu removeItem:menuItem];
|
||||
}
|
||||
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 : public ObjCClass<NSObject>
|
||||
{
|
||||
JuceMenuCallbackClass() : ObjCClass ("JUCEMainMenu_")
|
||||
{
|
||||
addIvar<JuceMainMenuHandler*> ("owner");
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (menuItemInvoked:), menuItemInvoked, "v@:@");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
addMethod (@selector (menuNeedsUpdate:), menuNeedsUpdate, "v@:@");
|
||||
addMethod (@selector (validateMenuItem:), validateMenuItem, "c@:@");
|
||||
|
||||
addProtocol (@protocol (NSMenuDelegate));
|
||||
|
||||
#if defined (MAC_OS_X_VERSION_10_14)
|
||||
addProtocol (@protocol (NSMenuItemValidation));
|
||||
#endif
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void setOwner (id self, JuceMainMenuHandler* owner)
|
||||
{
|
||||
object_setInstanceVariable (self, "owner", owner);
|
||||
}
|
||||
|
||||
private:
|
||||
static auto* getPopupMenuItem (NSMenuItem* item)
|
||||
{
|
||||
return getJuceClassFromNSObject<PopupMenu::Item> ([item representedObject]);
|
||||
}
|
||||
|
||||
static auto* getOwner (id self)
|
||||
{
|
||||
return getIvar<JuceMainMenuHandler*> (self, "owner");
|
||||
}
|
||||
|
||||
static void menuItemInvoked (id self, SEL, NSMenuItem* item)
|
||||
{
|
||||
if (auto* juceItem = getPopupMenuItem (item))
|
||||
getOwner (self)->invoke (*juceItem, static_cast<int> ([item tag]));
|
||||
}
|
||||
|
||||
static void menuNeedsUpdate (id self, SEL, NSMenu* menu)
|
||||
{
|
||||
getOwner (self)->updateTopLevelMenu (menu);
|
||||
}
|
||||
|
||||
static BOOL validateMenuItem (id, SEL, NSMenuItem* item)
|
||||
{
|
||||
if (auto* juceItem = getPopupMenuItem (item))
|
||||
return juceItem->isEnabled;
|
||||
|
||||
return YES;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
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 : 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void juce_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)
|
||||
{
|
||||
juce_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
|
181
deps/juce/modules/juce_gui_basics/native/juce_mac_MouseCursor.mm
vendored
Normal file
181
deps/juce/modules/juce_gui_basics/native/juce_mac_MouseCursor.mm
vendored
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
||||
|
||||
//==============================================================================
|
||||
namespace MouseCursorHelpers
|
||||
{
|
||||
static NSCursor* fromNSImage (NSImage* im, NSPoint hotspot)
|
||||
{
|
||||
NSCursor* c = [[NSCursor alloc] initWithImage: im
|
||||
hotSpot: hotspot];
|
||||
[im release];
|
||||
return c;
|
||||
}
|
||||
|
||||
static void* 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void* CustomMouseCursorInfo::create() const
|
||||
{
|
||||
return MouseCursorHelpers::fromNSImage (imageToNSImage (image, scaleFactor),
|
||||
NSMakePoint (hotspot.x, hotspot.y));
|
||||
}
|
||||
|
||||
void* MouseCursor::createStandardMouseCursor (MouseCursor::StandardCursorType type)
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
NSCursor* c = nil;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case NormalCursor:
|
||||
case ParentCursor: c = [NSCursor arrowCursor]; break;
|
||||
case NoCursor: return CustomMouseCursorInfo (Image (Image::ARGB, 8, 8, true), {}).create();
|
||||
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 (void* m = MouseCursorHelpers::fromHIServices ("resizenorthsouth"))
|
||||
return m;
|
||||
|
||||
c = [NSCursor resizeUpDownCursor];
|
||||
break;
|
||||
|
||||
case LeftRightResizeCursor:
|
||||
if (void* m = MouseCursorHelpers::fromHIServices ("resizeeastwest"))
|
||||
return m;
|
||||
|
||||
c = [NSCursor resizeLeftRightCursor];
|
||||
break;
|
||||
|
||||
case TopLeftCornerResizeCursor:
|
||||
case BottomRightCornerResizeCursor:
|
||||
return MouseCursorHelpers::fromHIServices ("resizenorthwestsoutheast");
|
||||
|
||||
case TopRightCornerResizeCursor:
|
||||
case BottomLeftCornerResizeCursor:
|
||||
return MouseCursorHelpers::fromHIServices ("resizenortheastsouthwest");
|
||||
|
||||
case UpDownLeftRightResizeCursor:
|
||||
return MouseCursorHelpers::fromHIServices ("move");
|
||||
|
||||
case NumStandardCursorTypes:
|
||||
default:
|
||||
jassertfalse;
|
||||
break;
|
||||
}
|
||||
|
||||
[c retain];
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
void MouseCursor::deleteMouseCursor (void* const cursorHandle, const bool /*isStandard*/)
|
||||
{
|
||||
[((NSCursor*) cursorHandle) release];
|
||||
}
|
||||
|
||||
void MouseCursor::showInWindow (ComponentPeer*) const
|
||||
{
|
||||
auto c = (NSCursor*) getHandle();
|
||||
|
||||
if (c == nil)
|
||||
c = [NSCursor arrowCursor];
|
||||
|
||||
[c set];
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void* CustomMouseCursorInfo::create() const { return nullptr; }
|
||||
void* MouseCursor::createStandardMouseCursor (MouseCursor::StandardCursorType) { return nullptr; }
|
||||
void MouseCursor::deleteMouseCursor (void*, bool) {}
|
||||
void MouseCursor::showInWindow (ComponentPeer*) const {}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
2563
deps/juce/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm
vendored
Normal file
2563
deps/juce/modules/juce_gui_basics/native/juce_mac_NSViewComponentPeer.mm
vendored
Normal file
File diff suppressed because it is too large
Load Diff
744
deps/juce/modules/juce_gui_basics/native/juce_mac_Windowing.mm
vendored
Normal file
744
deps/juce/modules/juce_gui_basics/native/juce_mac_Windowing.mm
vendored
Normal file
@ -0,0 +1,744 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class OSXMessageBox : private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
OSXMessageBox (const MessageBoxOptions& opts,
|
||||
std::unique_ptr<ModalComponentManager::Callback>&& c)
|
||||
: options (opts), callback (std::move (c))
|
||||
{
|
||||
}
|
||||
|
||||
int getResult() const
|
||||
{
|
||||
switch (getRawResult())
|
||||
{
|
||||
case NSAlertFirstButtonReturn: return 0;
|
||||
case NSAlertSecondButtonReturn: return 1;
|
||||
case NSAlertThirdButtonReturn: return 2;
|
||||
default: break;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return 0;
|
||||
}
|
||||
|
||||
using AsyncUpdater::triggerAsyncUpdate;
|
||||
|
||||
private:
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
auto result = getResult();
|
||||
|
||||
if (callback != nullptr)
|
||||
callback->modalStateFinished (result);
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
static void addButton (NSAlert* alert, const String& button)
|
||||
{
|
||||
if (! button.isEmpty())
|
||||
[alert addButtonWithTitle: juceStringToNS (button)];
|
||||
}
|
||||
|
||||
NSInteger getRawResult() const
|
||||
{
|
||||
NSAlert* alert = [[[NSAlert alloc] init] autorelease];
|
||||
|
||||
[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));
|
||||
|
||||
return [alert runModal];
|
||||
}
|
||||
|
||||
MessageBoxOptions options;
|
||||
std::unique_ptr<ModalComponentManager::Callback> callback;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OSXMessageBox)
|
||||
};
|
||||
|
||||
static int showDialog (const MessageBoxOptions& options,
|
||||
ModalComponentManager::Callback* callbackIn,
|
||||
AlertWindowMappings::MapFn mapFn)
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
if (callbackIn == nullptr)
|
||||
{
|
||||
jassert (mapFn != nullptr);
|
||||
|
||||
OSXMessageBox messageBox (options, nullptr);
|
||||
return mapFn (messageBox.getResult());
|
||||
}
|
||||
#endif
|
||||
|
||||
auto messageBox = std::make_unique<OSXMessageBox> (options,
|
||||
AlertWindowMappings::getWrappedCallback (callbackIn, mapFn));
|
||||
|
||||
messageBox->triggerAsyncUpdate();
|
||||
messageBox.release();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/)
|
||||
{
|
||||
showDialog (MessageBoxOptions()
|
||||
.withIconType (iconType)
|
||||
.withTitle (title)
|
||||
.withMessage (message)
|
||||
.withButton (TRANS("OK")),
|
||||
nullptr, AlertWindowMappings::messageBox);
|
||||
}
|
||||
|
||||
int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
|
||||
{
|
||||
return showDialog (options, nullptr, AlertWindowMappings::noMapping);
|
||||
}
|
||||
#endif
|
||||
|
||||
void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
showDialog (MessageBoxOptions()
|
||||
.withIconType (iconType)
|
||||
.withTitle (title)
|
||||
.withMessage (message)
|
||||
.withButton (TRANS("OK")),
|
||||
callback, AlertWindowMappings::messageBox);
|
||||
}
|
||||
|
||||
bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
return showDialog (MessageBoxOptions()
|
||||
.withIconType (iconType)
|
||||
.withTitle (title)
|
||||
.withMessage (message)
|
||||
.withButton (TRANS("OK"))
|
||||
.withButton (TRANS("Cancel")),
|
||||
callback, AlertWindowMappings::okCancel) != 0;
|
||||
}
|
||||
|
||||
int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
return showDialog (MessageBoxOptions()
|
||||
.withIconType (iconType)
|
||||
.withTitle (title)
|
||||
.withMessage (message)
|
||||
.withButton (TRANS("Yes"))
|
||||
.withButton (TRANS("No"))
|
||||
.withButton (TRANS("Cancel")),
|
||||
callback, AlertWindowMappings::yesNoCancel);
|
||||
}
|
||||
|
||||
int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType iconType,
|
||||
const String& title, const String& message,
|
||||
Component* /*associatedComponent*/,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
return showDialog (MessageBoxOptions()
|
||||
.withIconType (iconType)
|
||||
.withTitle (title)
|
||||
.withMessage (message)
|
||||
.withButton (TRANS("Yes"))
|
||||
.withButton (TRANS("No")),
|
||||
callback, AlertWindowMappings::okCancel);
|
||||
}
|
||||
|
||||
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
|
||||
ModalComponentManager::Callback* callback)
|
||||
{
|
||||
showDialog (options, callback, AlertWindowMappings::noMapping);
|
||||
}
|
||||
|
||||
void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
|
||||
std::function<void (int)> callback)
|
||||
{
|
||||
showAsync (options, ModalCallbackFunction::create (callback));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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;
|
||||
}
|
||||
|
||||
struct NSDraggingSourceHelper : public ObjCClass<NSObject<NSDraggingSource>>
|
||||
{
|
||||
NSDraggingSourceHelper() : ObjCClass<NSObject<NSDraggingSource>> ("JUCENSDraggingSourceHelper_")
|
||||
{
|
||||
addIvar<std::function<void()>*> ("callback");
|
||||
addIvar<String*> ("text");
|
||||
addIvar<NSDragOperation*> ("operation");
|
||||
|
||||
addMethod (@selector (dealloc), dealloc, "v@:");
|
||||
addMethod (@selector (pasteboard:item:provideDataForType:), provideDataForType, "v@:@@@");
|
||||
|
||||
addMethod (@selector (draggingSession:sourceOperationMaskForDraggingContext:), sourceOperationMaskForDraggingContext, "c@:@@");
|
||||
addMethod (@selector (draggingSession:endedAtPoint:operation:), draggingSessionEnded, "v@:@@@");
|
||||
|
||||
addProtocol (@protocol (NSPasteboardItemDataProvider));
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
private:
|
||||
static void 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));
|
||||
}
|
||||
|
||||
static void 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];
|
||||
}
|
||||
|
||||
static NSDragOperation sourceOperationMaskForDraggingContext (id self, SEL, NSDraggingSession*, NSDraggingContext)
|
||||
{
|
||||
return *getIvar<NSDragOperation*> (self, "operation");
|
||||
}
|
||||
|
||||
static void draggingSessionEnded (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()();
|
||||
}
|
||||
};
|
||||
|
||||
static NSDraggingSourceHelper draggingSourceHelper;
|
||||
|
||||
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 = [draggingSourceHelper.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];
|
||||
|
||||
if (auto session = [view beginDraggingSessionWithItems: [NSArray arrayWithObject: dragItem]
|
||||
event: event
|
||||
source: helper])
|
||||
{
|
||||
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 = [draggingSourceHelper.createInstance() autorelease];
|
||||
|
||||
if (callback != nullptr)
|
||||
NSDraggingSourceHelper::setCompletionCallback (helper, callback);
|
||||
|
||||
NSDraggingSourceHelper::setDragOperation (helper, canMoveFiles ? NSDragOperationMove
|
||||
: NSDragOperationCopy);
|
||||
|
||||
return [view beginDraggingSessionWithItems: dragItems
|
||||
event: event
|
||||
source: helper] != nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) };
|
||||
}
|
||||
}
|
||||
|
||||
void MouseInputSource::setRawMousePosition (Point<float> newPosition)
|
||||
{
|
||||
// this rubbish needs to be done around the warp call, to avoid causing a
|
||||
// bizarre glitch..
|
||||
CGAssociateMouseAndMouseCursorPosition (false);
|
||||
CGWarpMouseCursorPosition (convertToCGPoint (newPosition));
|
||||
CGAssociateMouseAndMouseCursorPosition (true);
|
||||
}
|
||||
|
||||
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")];
|
||||
}
|
||||
|
||||
class Desktop::NativeDarkModeChangeDetectorImpl
|
||||
{
|
||||
public:
|
||||
NativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
static DelegateClass delegateClass;
|
||||
|
||||
delegate = [delegateClass.createInstance() init];
|
||||
object_setInstanceVariable (delegate, "owner", this);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserver: delegate
|
||||
selector: @selector (darkModeChanged:)
|
||||
name: @"AppleInterfaceThemeChangedNotification"
|
||||
object: nil];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
~NativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
object_setInstanceVariable (delegate, "owner", nullptr);
|
||||
[[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate];
|
||||
[delegate release];
|
||||
}
|
||||
|
||||
void darkModeChanged()
|
||||
{
|
||||
Desktop::getInstance().darkModeChanged();
|
||||
}
|
||||
|
||||
private:
|
||||
struct DelegateClass : public ObjCClass<NSObject>
|
||||
{
|
||||
DelegateClass() : ObjCClass<NSObject> ("JUCEDelegate_")
|
||||
{
|
||||
addIvar<NativeDarkModeChangeDetectorImpl*> ("owner");
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (darkModeChanged:), darkModeChanged, "v@:@");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
static void darkModeChanged (id self, SEL, NSNotification*)
|
||||
{
|
||||
if (auto* owner = getIvar<NativeDarkModeChangeDetectorImpl*> (self, "owner"))
|
||||
owner->darkModeChanged();
|
||||
}
|
||||
};
|
||||
|
||||
id delegate = nil;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl)
|
||||
};
|
||||
|
||||
std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl()
|
||||
{
|
||||
return std::make_unique<NativeDarkModeChangeDetectorImpl>();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class ScreenSaverDefeater : 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)
|
||||
{
|
||||
IOReturn res = IOPMAssertionCreateWithName (kIOPMAssertionTypePreventUserIdleDisplaySleep,
|
||||
kIOPMAssertionLevelOn,
|
||||
CFSTR ("JUCE Playback"),
|
||||
&assertionID);
|
||||
jassert (res == kIOReturnSuccess); ignoreUnused (res);
|
||||
}
|
||||
|
||||
~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 : 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))
|
||||
if (thisPtr->forceDisplayUpdate != nullptr)
|
||||
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 = (mainScreenBottom == 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;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool juce_areThereAnyAlwaysOnTopWindows()
|
||||
{
|
||||
for (NSWindow* window in [NSApp windows])
|
||||
if ([window level] > NSNormalWindowLevel)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static void selectImageForDrawing (const Image& image)
|
||||
{
|
||||
[NSGraphicsContext saveGraphicsState];
|
||||
|
||||
if (@available (macOS 10.10, *))
|
||||
[NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithCGContext: juce_getImageContext (image)
|
||||
flipped: false]];
|
||||
else
|
||||
[NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithGraphicsPort: juce_getImageContext (image)
|
||||
flipped: false]];
|
||||
}
|
||||
|
||||
static void releaseImageAfterDrawing()
|
||||
{
|
||||
[[NSGraphicsContext currentContext] flushGraphics];
|
||||
[NSGraphicsContext restoreGraphicsState];
|
||||
}
|
||||
|
||||
Image juce_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
|
||||
{
|
||||
CGImageRef screenShot = CGWindowListCreateImage (CGRectNull,
|
||||
kCGWindowListOptionIncludingWindow,
|
||||
(CGWindowID) [nsWindow windowNumber],
|
||||
kCGWindowImageBoundsIgnoreFraming);
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
void Process::setDockIconVisible (bool isVisible)
|
||||
{
|
||||
ProcessSerialNumber psn { 0, kCurrentProcess };
|
||||
|
||||
OSStatus err = TransformProcessType (&psn, isVisible ? kProcessTransformToForegroundApplication
|
||||
: kProcessTransformToUIElementApplication);
|
||||
jassert (err == 0);
|
||||
ignoreUnused (err);
|
||||
}
|
||||
|
||||
} // namespace juce
|
368
deps/juce/modules/juce_gui_basics/native/juce_win32_DragAndDrop.cpp
vendored
Normal file
368
deps/juce/modules/juce_gui_basics/native/juce_win32_DragAndDrop.cpp
vendored
Normal file
@ -0,0 +1,368 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 : public ComBaseClassHelper<IDropSource>
|
||||
{
|
||||
JuceDropSource() {}
|
||||
|
||||
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 : 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 : public ComBaseClassHelper <IDataObject>
|
||||
{
|
||||
public:
|
||||
JuceDataObject (const FORMATETC* f, const STGMEDIUM* m)
|
||||
: format (f), medium (m)
|
||||
{
|
||||
}
|
||||
|
||||
~JuceDataObject()
|
||||
{
|
||||
jassert (refCount == 0);
|
||||
}
|
||||
|
||||
JUCE_COMRESULT GetData (FORMATETC* pFormatEtc, STGMEDIUM* pMedium)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
pFormatEtcOut->ptd = nullptr;
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT EnumFormatEtc (DWORD direction, IEnumFORMATETC** result)
|
||||
{
|
||||
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*) { return DATA_E_FORMATETC; }
|
||||
JUCE_COMRESULT SetData (FORMATETC*, STGMEDIUM*, BOOL) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT DAdvise (FORMATETC*, DWORD, IAdviseSink*, DWORD*) { return OLE_E_ADVISENOTSUPPORTED; }
|
||||
JUCE_COMRESULT DUnadvise (DWORD) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT EnumDAdvise (IEnumSTATDATA**) { return OLE_E_ADVISENOTSUPPORTED; }
|
||||
|
||||
private:
|
||||
const FORMATETC* const format;
|
||||
const STGMEDIUM* const medium;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (JuceDataObject)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
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 : 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
|
||||
{
|
||||
ignoreUnused (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 : 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 { 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
|
865
deps/juce/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp
vendored
Normal file
865
deps/juce/modules/juce_gui_basics/native/juce_win32_FileChooser.cpp
vendored
Normal file
@ -0,0 +1,865 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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_MINGW
|
||||
LWSTDAPI IUnknown_GetWindow (IUnknown* punk, HWND* phwnd);
|
||||
#endif
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
// Implemented in juce_win32_Messaging.cpp
|
||||
bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages);
|
||||
|
||||
class Win32NativeFileChooser : 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),
|
||||
isSave ((flags & FileBrowserComponent::saveMode) != 0),
|
||||
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 (! 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 : 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, bool async)
|
||||
{
|
||||
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 : 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 { 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); }
|
||||
|
||||
JUCE_COMRESULT updateHwnd (IFileDialog* d)
|
||||
{
|
||||
HWND hwnd = nullptr;
|
||||
IUnknown_GetWindow (d, &hwnd);
|
||||
|
||||
ScopedLock lock (owner.deletingDialog);
|
||||
|
||||
if (owner.shouldCancel)
|
||||
d->Close (S_FALSE);
|
||||
else if (hwnd != nullptr)
|
||||
owner.nativeDialogRef = hwnd;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
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 (async ? nullptr : static_cast<HWND> (owner->getWindowHandle())) == S_OK;
|
||||
}();
|
||||
|
||||
ScopedLock lock (deletingDialog);
|
||||
nativeDialogRef = nullptr;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Array<URL> openDialogVistaAndUp (bool async)
|
||||
{
|
||||
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, async);
|
||||
|
||||
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, async);
|
||||
|
||||
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 = (HWND) (async ? nullptr : owner->getWindowHandle());
|
||||
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 = (HWND) (async ? nullptr : owner->getWindowHandle());
|
||||
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 ! JUCE_MINGW
|
||||
if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista
|
||||
&& customComponent == nullptr)
|
||||
{
|
||||
return openDialogVistaAndUp (async);
|
||||
}
|
||||
#endif
|
||||
|
||||
return openDialogPreVista (async);
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
results = [&]
|
||||
{
|
||||
struct ScopedCoInitialize
|
||||
{
|
||||
// IUnknown_GetWindow will only succeed when instantiated in a single-thread apartment
|
||||
ScopedCoInitialize() { ignoreUnused (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
|
||||
{
|
||||
auto extension = filename.fromLastOccurrenceOf (".", false, false);
|
||||
|
||||
if (extension.isEmpty())
|
||||
{
|
||||
auto tokens = StringArray::fromTokens (filtersString, ";,", "\"'");
|
||||
tokens.trim();
|
||||
tokens.removeEmptyStrings();
|
||||
|
||||
if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty())
|
||||
extension = tokens[0].fromFirstOccurrenceOf (".", false, false);
|
||||
}
|
||||
|
||||
return extension;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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 : public std::enable_shared_from_this<Native>,
|
||||
public Component,
|
||||
public FileChooser::Pimpl
|
||||
{
|
||||
public:
|
||||
Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComp)
|
||||
: owner (fileChooser),
|
||||
nativeFileChooser (std::make_unique<Win32NativeFileChooser> (this, flags, 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 (juce_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;
|
||||
}
|
||||
|
||||
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
|
43
deps/juce/modules/juce_gui_basics/native/juce_win32_ScopedThreadDPIAwarenessSetter.h
vendored
Normal file
43
deps/juce/modules/juce_gui_basics/native/juce_win32_ScopedThreadDPIAwarenessSetter.h
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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
|
5251
deps/juce/modules/juce_gui_basics/native/juce_win32_Windowing.cpp
vendored
Normal file
5251
deps/juce/modules/juce_gui_basics/native/juce_win32_Windowing.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
594
deps/juce/modules/juce_gui_basics/native/x11/juce_linux_X11_DragAndDrop.cpp
vendored
Normal file
594
deps/juce/modules/juce_gui_basics/native/x11/juce_linux_X11_DragAndDrop.cpp
vendored
Normal file
@ -0,0 +1,594 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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 void* createDraggingHandCursor();
|
||||
extern 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();
|
||||
|
||||
auto dropPos = Desktop::getInstance().getDisplays().physicalToLogical (Point<int> ((int) clientMsg.data.l[2] >> 16,
|
||||
(int) clientMsg.data.l[2] & 0xffff));
|
||||
dropPos -= peer->getBounds().getPosition();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (completionCallback != nullptr)
|
||||
completionCallback();
|
||||
}
|
||||
|
||||
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 != None && 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();
|
||||
|
||||
if (! dragInfoCopy.isEmpty())
|
||||
if (auto* peer = getPeerFor (windowH))
|
||||
peer->handleDragDrop (dragInfoCopy);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
::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
|
240
deps/juce/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.cpp
vendored
Normal file
240
deps/juce/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.cpp
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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)
|
||||
|
||||
}
|
620
deps/juce/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.h
vendored
Normal file
620
deps/juce/modules/juce_gui_basics/native/x11/juce_linux_X11_Symbols.h
vendored
Normal file
@ -0,0 +1,620 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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)
|
||||
};
|
||||
|
||||
}
|
3837
deps/juce/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp
vendored
Normal file
3837
deps/juce/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
346
deps/juce/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.h
vendored
Normal file
346
deps/juce/modules/juce_gui_basics/native/x11/juce_linux_XWindowSystem.h
vendored
Normal file
@ -0,0 +1,346 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - 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 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-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:
|
||||
explicit XSettings (::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;
|
||||
|
||||
//==============================================================================
|
||||
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;
|
||||
|
||||
BorderSize<int> 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;
|
||||
|
||||
void* createCustomMouseCursorInfo (const Image&, Point<int> hotspot) const;
|
||||
void deleteMouseCursor (void* cursorHandle) const;
|
||||
void* createStandardMouseCursor (MouseCursor::StandardCursorType) const;
|
||||
void showCursor (::Window, void* cursorHandle) const;
|
||||
|
||||
bool isKeyCurrentlyDown (int keyCode) const;
|
||||
ModifierKeys getNativeRealtimeModifiers() const;
|
||||
|
||||
Array<Displays::Display> findDisplays (float masterScale) const;
|
||||
|
||||
::Window createKeyProxy (::Window) const;
|
||||
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; }
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
::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