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:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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)
};
}

View 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

View 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

View 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)
};
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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"

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}
}

View 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;
}

View File

@ -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);
}

View 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);
}
}

View 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);
}
}
}

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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: &notificationSettings 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: &notification 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: &notification 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: &notification 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: &center atIndex:2];
[invocation setArgument: &notification 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: &center 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

View 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

View 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

View 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
}
}

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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)
}

View 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)
};
}

File diff suppressed because it is too large Load Diff

View 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