Files
UniWindow-Controller/Xcode/LibUniWinC/LibUniWinC.swift
2021-10-09 22:48:34 +09:00

1101 lines
41 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Unified Window Controller macOS plugin
//
// Author: Kirurobo
// License: MIT
//
// Acknowledgement:
// This code is based on transparent.swift created by kriver1 on 2018/05/23.
// https://qiita.com/KRiver1/items/9ecf65759cf1349f56af
//
// References:
// - https://qiita.com/KRiver1/items/9ecf65759cf1349f56af
// - http://tatsudoya.blog.fc2.com/blog-entry-244.html
// - https://qiita.com/mybdesign/items/fe3e390741799c1814ad
// - https://blog.fenrir-inc.com/jp/2011/07/nsview_uiview.html
//
import Foundation
import Cocoa
/// Window controller main logic
@objcMembers
public class LibUniWinC : NSObject {
// MARK: - Internal structs and classes
///
private struct State {
public var isReady: Bool = false
public var isTopmost: Bool = false
public var isBottommost: Bool = false
public var isBorderless: Bool = false
public var isTransparent: Bool = false
// 使
public var isZoomed: Bool = false
// Keep unzoomed size for the borderless window
public var normalWindowRect: NSRect = NSRect(x: 0, y: 0, width: 0, height: 0)
}
/// WindowStyleChanged
private enum EventType : Int32 {
case None = 0
case Style = 1
case Size = 2
}
public struct PanelSettings {
public var structSize: Int32 = 0;
public var flags: Int32 = 0;
public var titleText: UnsafePointer<UniChar>?;
public var filterText: UnsafePointer<UniChar>?;
public var initialFile: UnsafePointer<UniChar>?;
public var initialDirectory: UnsafePointer<UniChar>?;
public var defaultExt: UnsafePointer<UniChar>?;
}
///
private class OriginalWindowInfo {
/// StyleMask
public var styleMask: NSWindow.StyleMask = []
/// CollectionBehavior
public var collectionBehavior: NSWindow.CollectionBehavior = []
/// Level
public var level: NSWindow.Level = NSWindow.Level.normal
public var titlebarAppearsTransparent: Bool = false
public var titleVisibility: NSWindow.TitleVisibility = NSWindow.TitleVisibility.visible
public var backgroundColor: NSColor = NSColor.clear
public var isOpaque: Bool = true
public var hasShadow: Bool = true
public var contentViewWantsLayer: Bool = true
public var contentViewLayerIsOpaque: Bool = true
public var contentViewLayerBackgroundColor: CGColor? = CGColor.clear
///
public func Store(window: NSWindow) -> Void {
self.collectionBehavior = window.collectionBehavior
self.styleMask = window.styleMask
self.level = window.level
self.titlebarAppearsTransparent = window.titlebarAppearsTransparent
self.titleVisibility = window.titleVisibility
self.backgroundColor = window.backgroundColor
self.isOpaque = window.isOpaque
self.hasShadow = window.hasShadow
if let view = window.contentView {
self.contentViewWantsLayer = view.wantsLayer
if let layer = view.layer {
self.contentViewLayerIsOpaque = layer.isOpaque
self.contentViewLayerBackgroundColor = layer.backgroundColor
}
}
}
///
public func Restore(window: NSWindow) -> Void {
window.collectionBehavior = self.collectionBehavior
window.styleMask = self.styleMask
window.level = self.level
window.titlebarAppearsTransparent = self.titlebarAppearsTransparent
window.titleVisibility = self.titleVisibility
window.backgroundColor = self.backgroundColor
window.isOpaque = self.isOpaque
window.hasShadow = self.hasShadow
window.contentView?.wantsLayer = self.contentViewWantsLayer
window.contentView?.layer?.isOpaque = self.contentViewLayerIsOpaque
window.contentView?.layer?.backgroundColor = self.contentViewLayerBackgroundColor
}
}
// MARK: - Static variables
/// nil
private static var targetWindow: NSWindow? = nil
///
private static var state: State = State()
///
private static var orgWindowInfo: OriginalWindowInfo = OriginalWindowInfo()
/// Callback function with wchar_t pointer
public typealias stringCallback = (@convention(c) (UnsafeRawPointer) -> Void)
public typealias intCallback = (@convention(c) (Int32) -> Void)
public static var dropFilesCallback: stringCallback? = nil
public static var openFilesCallback: stringCallback? = nil
public static var saveFilesCallback: stringCallback? = nil
public static var monitorChangedCallback: intCallback? = nil
public static var windowStyleChangedCallback: intCallback? = nil
private static var observerObject: Any? = nil
/// Sub view to implement file dropping
private static var overlayView: OverlayView? = nil
///
private static var primaryMonitorHeight: CGFloat = 0
private static var monitorCount: Int = 0
private static var monitorRectangles: [CGRect] = []
private static var monitorIndices: [Int] = []
private static let openPanel = NSOpenPanel()
private static let savePanel = NSSavePanel()
// MARK: - Properties
///
/// - Returns: true
@objc public static func isActive() -> Bool {
if (state.isReady && targetWindow == nil) {
return false
}
return true
}
@objc public static func isTransparent() -> Bool {
return state.isTransparent
}
@objc public static func isBorderless() -> Bool {
return state.isBorderless
}
@objc public static func isTopmost() -> Bool {
return state.isTopmost
}
@objc public static func isBottommost() -> Bool {
return state.isBottommost
}
@objc public static func isMaximized() -> Bool {
return state.isZoomed
//return _isZoomedActually()
}
@objc public static func isMinimized() -> Bool {
return (targetWindow?.isMiniaturized ?? false)
}
private static func _isZoomedActually() -> Bool {
if (targetWindow == nil) {
return false
} else if (targetWindow!.isMiniaturized) {
return false
} else if (state.isTransparent) {
// When the window is transparent
let monitorIndex = getCurrentMonitor()
let rect = monitorRectangles[monitorIndices[Int(monitorIndex)]]
let frame = targetWindow!.frame
return (frame.size == rect.size) && (frame.origin == rect.origin)
} else {
// When the window is opaque
return targetWindow!.isZoomed
}
}
// MARK: - Initialize, window handling
/// Initialize
private static func _setup() -> Void {
// Get the screen size
_updateScreenInfo()
// Prepare notification to refresh the screen size
NotificationCenter.default.addObserver(
forName: NSApplication.didChangeScreenParametersNotification,
object: NSApplication.shared,
queue: OperationQueue.main
) {
notification -> Void in _onMonitorChanged()
}
// Flag as initialized
state.isReady = true
}
/// Called when screen parameeters changed
private static func _onMonitorChanged() -> Void {
_updateScreenInfo()
// Run callback
let count = getMonitorCount()
monitorChangedCallback?(count)
}
/// Retrieve current monitor settings
private static func _updateScreenInfo() -> Void {
// Reference: https://stackoverrun.com/ja/q/1746184
primaryMonitorHeight = NSScreen.screens.map {$0.frame.origin.y + $0.frame.height}.max()!
// Get the number of monitors
monitorCount = NSScreen.screens.count
// Clear the list
monitorRectangles.removeAll()
monitorIndices.removeAll()
// Get each screen rectangle
for i in 0..<monitorCount {
let screen = NSScreen.screens[i]
//monitorRectangles.append(screen.visibleFrame)
monitorRectangles.append(screen.frame)
monitorIndices.append(i)
}
// Sort the list so that the top left monitor is at the zero
monitorIndices = monitorIndices.sorted(by: {
(monitorRectangles[$0].minX < monitorRectangles[$1].minX)
|| (monitorRectangles[$0].minX == monitorRectangles[$1].minX && monitorRectangles[$0].maxY < monitorRectangles[$1].maxY)
})
}
/// Find my own window
private static func _findMyWindow() -> NSWindow {
let myWindow: NSWindow = NSApp.orderedWindows[0]
return myWindow
}
/// Detach from the window
@objc public static func detachWindow() -> Void {
_detachWindow()
}
/// Attach to my main window
@objc public static func attachMyWindow() -> Bool {
let window: NSWindow = _findMyWindow()
_attachWindow(window: window)
return true
}
/// Set the target window
/// Restore the former winodw if exist
private static func _attachWindow(window: NSWindow) -> Void {
// Do nothing if the same window is the target
if (targetWindow == window) {
return
}
// Release the former window if exist
detachWindow()
// Initialize when the first call
if (!state.isReady) {
_setup()
}
// Set to the target
targetWindow = window
// Store the original state
orgWindowInfo.Store(window: window)
// Apply the state
_reapplyWindowStyles()
// Add observers for window state changed callback and reapply styles
let center = NotificationCenter.default
center.addObserver(self, selector: #selector(_fullScreenChangedObserver(notification:)), name: NSWindow.didEnterFullScreenNotification, object: window)
center.addObserver(self, selector: #selector(_fullScreenChangedObserver(notification:)), name: NSWindow.didExitFullScreenNotification, object: window)
center.addObserver(self, selector: #selector(_windowStateChangedObserver(notification:)), name: NSWindow.didMiniaturizeNotification, object: window)
center.addObserver(self, selector: #selector(_windowStateChangedObserver(notification:)), name: NSWindow.didDeminiaturizeNotification, object: window)
//center.addObserver(self, selector: #selector(_resizedObserver(notification:)), name: NSWindow.didResizeNotification, object: window)
center.addObserver(self, selector: #selector(_resizedObserver(notification:)), name: NSWindow.didEndLiveResizeNotification, object: window)
center.addObserver(self, selector: #selector(_keepBottommostObserver(notification:)), name: NSWindow.didBecomeKeyNotification, object: window)
}
private static func _detachWindow() -> Void {
if (targetWindow != nil) {
let center = NotificationCenter.default
center.removeObserver(self, name: NSWindow.didEnterFullScreenNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didExitFullScreenNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didMiniaturizeNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didDeminiaturizeNotification, object: targetWindow)
//center.removeObserver(self, name: NSWindow.didResizeNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didEndLiveResizeNotification, object: targetWindow)
center.removeObserver(self, name: NSWindow.didBecomeKeyNotification, object: targetWindow)
//center.removeObserver(self)
// Restore the original style
orgWindowInfo.Restore(window: targetWindow!)
// Remove the subview
if (overlayView != nil) {
overlayView?.removeFromSuperview()
overlayView = nil
}
// Close dialog if it is opened
openPanel.close()
targetWindow = nil
}
}
@objc static func _fullScreenChangedObserver(notification: Notification) {
// Reapply the state at fullscreen
_reapplyWindowStyles()
_doWindowStyleChangedCallback(num: EventType.Size)
}
@objc static func _windowStateChangedObserver(notification: Notification) {
_doWindowStyleChangedCallback(num: EventType.Size)
}
@objc static func _resizedObserver(notification: Notification) {
if (targetWindow != nil) {
let zoomed = _isZoomedActually()
if (state.isZoomed != zoomed) {
state.isZoomed = zoomed
}
_doWindowStyleChangedCallback(num: EventType.Size)
}
}
@objc static func _keepBottommostObserver(notification: Notification) {
if ((targetWindow != nil) && (state.isBottommost)) {
targetWindow!.level = orgWindowInfo.level
targetWindow!.order(NSWindow.OrderingMode.below, relativeTo:0)
_doWindowStyleChangedCallback(num: EventType.Style)
}
}
/// Call this periodically to maintain window state.
@objc public static func update() {
if (targetWindow != nil) {
if (state.isTransparent) {
// Keep window transparent
if (targetWindow!.isOpaque) {
_setWindowTransparent(window: targetWindow!, isTransparent: true)
}
// Keep contentView transparent
if (targetWindow!.contentView?.layer?.isOpaque ?? false) {
_setContentViewTransparent(window: targetWindow!, isTransparent: true)
}
}
}
}
private static func _doWindowStyleChangedCallback(num : EventType) -> Void {
windowStyleChangedCallback?(num.rawValue)
}
/// Create an overlay view to handle file dropping
private static func _setupOverlayView() -> Void {
guard let window = targetWindow
else {
return
}
// Add a subview to handle file dropping
overlayView = OverlayView(frame: window.frame)
window.contentView?.addSubview(overlayView!)
overlayView?.fitToSuperView()
}
/// Apply current window state
private static func _reapplyWindowStyles() -> Void {
if (targetWindow != nil) {
if (state.isBottommost) {
setBottommost(isBottommost: state.isBottommost)
} else {
setTopmost(isTopmost: state.isTopmost)
}
setBorderless(isBorderless: state.isBorderless)
setTransparent(isTransparent: state.isTransparent)
setMaximized(isZoomed: state.isZoomed)
}
}
/// Copy UTF-16 string to uint16 buffer and add null for the end of the string
private static func _copyUTF16ToBuffer(text: String.UTF16View, buffer: UnsafeMutablePointer<UTF16Char>) -> Bool {
let count = text.count
if (count <= 0) {
return false
}
var i = 0
for c in text {
buffer[i] = c
i += 1
}
buffer[count] = UTF16Char.zero // End of the string
return true
}
// MARK: - Functions to get or set the window state
///
/// - Parameters:
/// - window:
/// - isTransparent: truefalse
private static func _setWindowTransparent(window: NSWindow, isTransparent: Bool) -> Void {
if (isTransparent) {
//window.styleMask = orgWindowInfo.styleMask
//window.styleMask = []
// if (state.isBorderless) {
// window.styleMask.insert(.borderless)
// }
window.backgroundColor = NSColor.clear
window.isOpaque = false
window.hasShadow = false
//window.contentView?.wantsLayer = true
} else {
// window.styleMask = orgWindowInfo.styleMask
// if (state.isBorderless) {
// window.styleMask.insert(.borderless)
// }
window.backgroundColor = orgWindowInfo.backgroundColor
window.isOpaque = orgWindowInfo.isOpaque
window.hasShadow = orgWindowInfo.hasShadow
}
}
/// ContentView
/// - Parameters:
/// - window:
/// - isTransparent: truefalse
private static func _setContentViewTransparent(window: NSWindow, isTransparent: Bool) -> Void {
if let view: NSView = window.contentView {
if (isTransparent) {
view.wantsLayer = true
view.layer?.backgroundColor = CGColor.clear
view.layer?.isOpaque = false
} else {
view.wantsLayer = orgWindowInfo.contentViewWantsLayer
view.layer?.backgroundColor = orgWindowInfo.contentViewLayerBackgroundColor
view.layer?.isOpaque = orgWindowInfo.contentViewLayerIsOpaque
}
}
}
///
/// - Parameters:
/// - window:
/// - isBorderless:
private static func _setWindowBorderless(window: NSWindow, isBorderless: Bool) -> Void {
if (isBorderless) {
window.styleMask.insert(.borderless)
window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
} else {
if (!orgWindowInfo.styleMask.contains(.borderless)) {
// .borderless
window.styleMask.remove(.borderless)
}
window.titlebarAppearsTransparent = orgWindowInfo.titlebarAppearsTransparent
window.titleVisibility = orgWindowInfo.titleVisibility
}
}
///
/// Windows
/// - Parameter type: 0:None, 1:Alpha, 2:ColorKey
@objc public static func setTransparentType(type: Int32) -> Void {
}
///
/// Windows
/// - Parameter color:
@objc public static func setKeyColor(color: Int32) -> Void {
}
///
/// - Parameter isTransparent: true
@objc public static func setTransparent(isTransparent: Bool) -> Void {
if let window: NSWindow = targetWindow {
_setWindowTransparent(window: window, isTransparent: isTransparent)
_setContentViewTransparent(window: window, isTransparent: isTransparent)
// Reapply borderless state
_setWindowBorderless(window: window, isBorderless: state.isBorderless)
}
if (state.isTransparent != isTransparent) {
_doWindowStyleChangedCallback(num: EventType.Style)
}
state.isTransparent = isTransparent
}
/// Hide or show the window border
/// - Parameter isBorderless: true for borderless
@objc public static func setBorderless(isBorderless: Bool) -> Void {
if let window: NSWindow = targetWindow {
if (!state.isZoomed) {
//if ((!isBorderless && state.isBorderless) || (isBorderless && !state.isBorderless && !_isZoomedActually())) {
if (isBorderless != state.isBorderless) {
// Store the window size when the window become borderless
state.normalWindowRect = window.frame
}
}
_setWindowBorderless(window: window, isBorderless: isBorderless)
if (state.isZoomed) {
if (!window.isZoomed) {
window.zoom(nil)
}
if (isBorderless) {
// Stretch to the full-screen size
let monitorIndex = getCurrentMonitor()
let rect = monitorRectangles[monitorIndices[Int(monitorIndex)]]
window.setFrame(rect, display: true, animate: false)
}
} else {
if (!isBorderless && state.isBorderless) {
// Restore the window size when the window become bordered
if (state.normalWindowRect.width != 0 && state.normalWindowRect.height != 0) {
window.setFrame(state.normalWindowRect, display: true, animate: false)
}
}
}
}
if (state.isBorderless != isBorderless) {
_doWindowStyleChangedCallback(num: EventType.Style)
}
state.isBorderless = isBorderless
}
///
/// - Parameter isTopmost: true for topmost (higher than the menu bar)
@objc public static func setTopmost(isTopmost: Bool) -> Void {
if let window: NSWindow = targetWindow {
if (isTopmost) {
window.collectionBehavior = [.fullScreenAuxiliary]
window.level = NSWindow.Level.popUpMenu
} else {
window.collectionBehavior = orgWindowInfo.collectionBehavior
window.level = orgWindowInfo.level
}
}
if (state.isTopmost != isTopmost) {
_doWindowStyleChangedCallback(num: EventType.Style)
}
state.isTopmost = isTopmost
state.isBottommost = false
}
///
/// - Parameter isBottommost: true
@objc public static func setBottommost(isBottommost: Bool) -> Void {
if let window: NSWindow = targetWindow {
if (isBottommost) {
window.collectionBehavior = [.fullScreenAuxiliary]
window.level = orgWindowInfo.level
window.order(NSWindow.OrderingMode.below, relativeTo:0)
} else {
window.collectionBehavior = orgWindowInfo.collectionBehavior
window.level = orgWindowInfo.level
}
}
if (state.isBottommost != isBottommost) {
_doWindowStyleChangedCallback(num: EventType.Style)
}
state.isBottommost = isBottommost
state.isTopmost = false
}
///
@objc public static func setClickThrough(isTransparent: Bool) -> Void {
targetWindow!.ignoresMouseEvents = isTransparent
}
/// Maximize the window
@objc public static func setMaximized(isZoomed: Bool) -> Void {
if let window: NSWindow = targetWindow {
if (state.isBorderless) {
// window.zoom() is unavailable if the window is ransparent (borderless)
if (isZoomed) {
// Store the window size when the window become zoomed
//if (!state.isZoomed && state.isBorderless && !_isZoomedActually()) {
if (!_isZoomedActually() && state.isBorderless) {
state.normalWindowRect = window.frame
}
// The window couldn't be zoomed when it is borderless
let monitorIndex = getCurrentMonitor()
let rect = monitorRectangles[monitorIndices[Int(monitorIndex)]]
window.setFrame(rect, display: true, animate: false)
} else {
if (state.normalWindowRect.width != 0 && state.normalWindowRect.height != 0) {
window.setFrame(state.normalWindowRect, display: true, animate: false)
}
}
state.isZoomed = isZoomed
} else {
// The window is opaque
if (window.isZoomed != isZoomed) {
// Toggle
window.zoom(nil)
state.isZoomed = window.isZoomed
}
}
} else {
// Remember the state
state.isZoomed = isZoomed
}
}
///
/// - Parameters:
/// - x:
/// - y:
/// - Returns: true
@objc public static func setPosition(x: Float32, y: Float32) -> Bool {
if (targetWindow == nil) {
return false
}
//// Windows
//let cocoaY = primaryMonitorHeight - CGFloat(y)
//let position: NSPoint = NSMakePoint(CGFloat(x), cocoaY)
//targetWindow?.setFrameTopLeftPoint(position)
//
let position: NSPoint = NSMakePoint(CGFloat(x), CGFloat(y))
targetWindow?.setFrameOrigin(position)
return true
}
///
/// - x:
/// - y:
/// - Returns: true
@objc public static func getPosition(x: UnsafeMutablePointer<Float32>, y: UnsafeMutablePointer<Float32>) -> Bool {
if (targetWindow == nil) {
x.pointee = 0;
y.pointee = 0;
return false
}
let frame = targetWindow!.frame
x.pointee = Float32(frame.minX)
y.pointee = Float32(frame.minY)
// Windows
//y.pointee = Float32(primaryMonitorHeight - frame.maxY)
return true
}
///
/// - Parameters:
/// - width:
/// - height:
/// - Returns: true
@objc public static func setSize(width: Float32, height:Float32) -> Bool {
if (targetWindow == nil) {
return false
}
var frame = targetWindow!.frame
frame.size.width = CGFloat(width)
frame.size.height = CGFloat(height)
targetWindow?.setFrame(frame, display: true, animate: false)
return true
}
///
/// - Parameters:
/// - width:
/// - height:
/// - Returns: true
@objc public static func getSize(width: UnsafeMutablePointer<Float32>, height: UnsafeMutablePointer<Float32>) -> Bool {
if (targetWindow == nil) {
width.pointee = 0;
height.pointee = 0;
return false
}
let currentSize = targetWindow!.frame.size
width.pointee = Float32(currentSize.width)
height.pointee = Float32(currentSize.height)
return true
}
@objc public static func registerWindowStyleChangedCallback(callback: @escaping intCallback) -> Bool {
windowStyleChangedCallback = callback
return true
}
@objc public static func unregisterWindowStyleChangedCallback() -> Bool {
windowStyleChangedCallback = nil
return true
}
// MARK: - Monitor Info.
///
/// - Returns:
@objc public static func getCurrentMonitor() -> Int32 {
var primaryMonitorIndex: Int = 0
//
if (targetWindow == nil) {
for i in 0..<monitorCount {
let screen = NSScreen.screens[monitorIndices[i]]
let sf = screen.visibleFrame
// 
if (sf.minX == 0 && sf.minY == 0) {
primaryMonitorIndex = i
break;
}
}
return Int32(primaryMonitorIndex)
}
//
let frame = targetWindow!.frame;
let cx: CGFloat = (frame.minX + frame.maxX) / 2.0
let cy: CGFloat = (frame.minY + frame.maxY) / 2.0
for i in 0..<monitorCount {
let screen = NSScreen.screens[monitorIndices[i]]
let sf = screen.visibleFrame
//
if (sf.minX <= cx && cx <= sf.maxX && sf.minY <= cy && cy <= sf.maxY) {
return Int32(i)
}
// 
if (sf.minX == 0 && sf.minY == 0) {
primaryMonitorIndex = i
}
}
return Int32(primaryMonitorIndex)
}
///
/// - Returns:
@objc public static func getMonitorCount() -> Int32 {
// NOTE: UnityScreenDisplayMonitor
return Int32(monitorCount)
}
///
/// - Parameters:
/// - monitorIndex:
/// - x: X
/// - y: Y
/// - width:
/// - height:
/// - Returns: true
@objc public static func getMonitorRectangle(
monitorIndex: Int32,
x: UnsafeMutablePointer<Float32>, y: UnsafeMutablePointer<Float32>,
width: UnsafeMutablePointer<Float32>, height: UnsafeMutablePointer<Float32>
) -> Bool {
// false
if (monitorIndex < 0 || monitorIndex >= monitorCount || monitorIndex >= NSScreen.screens.count) {
return false
}
let frame = NSScreen.screens[monitorIndices[Int(monitorIndex)]].visibleFrame
x.pointee = Float32(frame.minX)
y.pointee = Float32(frame.minY)
width.pointee = Float32(frame.width)
height.pointee = Float32(frame.height)
return true
}
@objc public static func registerMonitorChangedCallback(callback: @escaping intCallback) -> Bool {
monitorChangedCallback = callback
return true
}
@objc public static func unregisterMonitorChangedCallback() -> Bool {
monitorChangedCallback = nil
return true
}
// MARK: - File drop
@objc public static func setAllowDrop(enabled: Bool) -> Bool {
if (overlayView == nil) {
_setupOverlayView()
}
overlayView?.setEnabled(enabled: enabled)
return true
}
@objc public static func registerDropFilesCallback(callback: @escaping stringCallback) -> Bool {
dropFilesCallback = callback
return true
}
@objc public static func unregisterDropFilesCallback() -> Bool {
dropFilesCallback = nil
return true
}
// MARK: - Mouser curosor
///
/// - Parameters:
/// - x: X
/// - y: Y
/// - Returns: true
@objc public static func getCursorPosition(x: UnsafeMutablePointer<Float32>, y: UnsafeMutablePointer<Float32>) -> Bool {
let mousePos = NSEvent.mouseLocation
x.pointee = Float32(mousePos.x)
y.pointee = Float32(mousePos.y)
return true
}
///
/// - Parameters:
/// - x: X
/// - y: Y
/// - Returns: true
@objc public static func setCursorPosition(x: Float32, y: Float32) -> Bool {
let position = NSMakePoint(CGFloat(x), CGFloat(y))
let moveEvent = CGEvent(mouseEventSource: nil, mouseType: .mouseMoved,
mouseCursorPosition: position, mouseButton: .left)
moveEvent?.post(tap: .cgSessionEventTap)
return true
}
// MARK: - File dialogs
/// Open dialog
/// - Parameters:
/// -
@objc public static func openFilePanel(lpSettings: UnsafeRawPointer, lpBuffer: UnsafeMutablePointer<UniChar>?, bufferSize: UInt32) -> Bool {
let panel = openPanel
let pPanelSettings = lpSettings.bindMemory(to: PanelSettings.self, capacity: MemoryLayout<PanelSettings>.size)
let ps = pPanelSettings.pointee
let initialDir = getStringFromUtf16Array(textPointer: ps.initialDirectory)
let initialFile = getStringFromUtf16Array(textPointer: ps.initialFile) as NSString
panel.canChooseFiles = true
panel.canChooseDirectories = false
panel.allowsMultipleSelection = (ps.flags & 4 > 0)
panel.showsHiddenFiles = false
panel.allowedFileTypes = ["jpg", "png", "jpeg"]
panel.allowsOtherFileTypes = false
panel.title = getStringFromUtf16Array(textPointer: ps.titleText)
if (initialDir != "") {
panel.directoryURL = URL(fileURLWithPath: initialDir, isDirectory: true)
} else if (initialFile.deletingLastPathComponent != "") {
savePanel.directoryURL = URL(fileURLWithPath: initialFile.deletingLastPathComponent, isDirectory: true)
}
panel.nameFieldStringValue = initialFile.lastPathComponent
panel.accessoryView = createAccesoryViewForExtensions(text: getStringFromUtf16Array(textPointer: ps.filterText), parent: panel)
panel.canCreateDirectories = true //(ps.flags & 16 > 0)
//panel.showsTagField = false
panel.allowsOtherFileTypes = false
panel.level = NSWindow.Level.popUpMenu
panel.orderFrontRegardless()
let result = panel.runModal();
var text: String = ""
if (result == .OK) {
if (panel.urls.count > 0) {
// Make new-line separated string
for url in panel.urls {
text += "\"" + url.path.replacingOccurrences(of: "\"", with: "\"\"") + "\"\n"
//text += '"' + url.path + '"' + "\n"
}
}
}
return outputToStringBuffer(text: text, lpBuffer: lpBuffer, bufferSize: bufferSize)
}
@objc public static func openSavePanel(lpSettings: UnsafeRawPointer, lpBuffer: UnsafeMutablePointer<UniChar>?, bufferSize: UInt32) -> Bool {
let panel = savePanel
let pPanelSettings = lpSettings.bindMemory(to: PanelSettings.self, capacity: MemoryLayout<PanelSettings>.size)
let ps = pPanelSettings.pointee;
let initialDir = getStringFromUtf16Array(textPointer: ps.initialDirectory)
let initialFile = getStringFromUtf16Array(textPointer: ps.initialFile) as NSString
panel.showsHiddenFiles = false
panel.title = getStringFromUtf16Array(textPointer: ps.titleText)
if (initialDir != "") {
panel.directoryURL = URL(fileURLWithPath: initialDir, isDirectory: true)
} else if (initialFile.deletingLastPathComponent != "") {
panel.directoryURL = URL(fileURLWithPath: initialFile.deletingLastPathComponent, isDirectory: true)
}
panel.nameFieldStringValue = initialFile.lastPathComponent
panel.allowedFileTypes = ["jpg", "png", "jpeg"]
panel.accessoryView = createAccesoryViewForExtensions(text: getStringFromUtf16Array(textPointer: ps.filterText), parent: panel)
panel.canCreateDirectories = true //(ps.flags & 16 > 0)
//panel.showsTagField = false
panel.level = NSWindow.Level.popUpMenu
panel.orderFrontRegardless()
let result = panel.runModal();
var text: String = ""
if (result == .OK && (panel.url != nil)) {
let url: String = panel.url!.path
text = "\"" + url.replacingOccurrences(of: "\"", with: "\"\"") + "\"\n"
}
return outputToStringBuffer(text: text, lpBuffer: lpBuffer, bufferSize: bufferSize)
}
//
// /// Open folder (Not in used)
// @objc public static func openFolderPanel(lpSettings: UnsafeRawPointer, lpBuffer: UnsafeMutableRawPointer, bufferSize: UInt32) -> Bool {
// let panel = openPanel
//
// let pPanelSettings = lpSettings.bindMemory(to: PanelSettings.self, capacity: MemoryLayout<PanelSettings>.size)
// let ps = pPanelSettings.pointee;
//
// panel.canChooseFiles = false
// panel.canChooseDirectories = true
// panel.allowsMultipleSelection = (ps.flags & 4 > 0)
// panel.canCreateDirectories = (ps.flags & 16 > 0)
//
// panel.prompt = "Open"
// panel.level = NSWindow.Level.popUpMenu
// panel.orderFrontRegardless()
//
// let result = panel.runModal();
//
// var text: String = ""
// if (result == .OK) {
// if (panel.urls.count > 0) {
// // Make new-line separated string
// for url in panel.urls {
// text += "\"" + url.path.replacingOccurrences(of: "\"", with: "\"\"") + "\"\n"
// //text += '"' + url.path + '"' + "\n"
// }
// }
// }
// return outputToStringBuffer(text: text, lpBuffer: lpBuffer, bufferSize: bufferSize)
// }
/// Parse an UTF-16 null terminated string pointer to String
private static func getStringFromUtf16Array(textPointer: UnsafePointer<UniChar>?) -> String {
if (textPointer == nil) {
return ""
}
var len = 0
while textPointer![len] != UniChar.zero {
len += 1
}
return String(utf16CodeUnits: textPointer!, count: len)
}
private static func createAccesoryViewForExtensions(text: String, parent: NSSavePanel) -> NSView {
let accesoryView = NSView(frame: NSRect(x:0, y:0, width:270, height:50))
let popup = NSPopUpButton(frame: NSRect(x:130, y:10, width:120, height:25))
let label = NSTextField(frame: NSRect(x:20, y:15, width:100, height:18))
popup.addItems(withTitles: ["png", "jpg", "tiff"])
//popup.action = #selector(parent.allowedFileTypes = [popup.selectedItem!.title])
label.stringValue = "File types :"
label.isBordered = false
label.isSelectable = false
label.isEditable = false
accesoryView.addSubview(popup)
accesoryView.addSubview(label)
return accesoryView
}
/// Call a StringCallback with UTF-16 paramete
/// - Parameters:
/// - callback: Registered callback function
/// - text: Parrameter as String
/// - Returns: True if success
public static func callStringCallback(callback: stringCallback?, text: String) -> Bool {
if (callback == nil)
{
return false
}
let count = text.utf16.count
if (count <= 0) {
return false
}
let buffer = UnsafeMutablePointer<UniChar>.allocate(capacity: count + 1)
var i = 0
for c in text.utf16 {
buffer[i] = c
i += 1
}
buffer[count] = UniChar.zero // End of the string
// Do callback
callback?(buffer)
buffer.deallocate()
return true
}
/// Return an UTF-16 string by using a pointer
/// - Parameters:
/// - text: Parrameter as String
/// - lpBuffer: UTF-16 string buffer that allocated by caller
/// - bufferSize: Size of the string buffer
/// - Returns: True if success
private static func outputToStringBuffer(text: String, lpBuffer: UnsafeMutablePointer<UniChar>?, bufferSize: UInt32) -> Bool {
let size = Int(bufferSize)
//let buffer = lpBuffer.bindMemory(to: UniChar.self, capacity: size)
guard let buffer = lpBuffer else {
return false
}
// Fill in zero
for i in 0..<size {
buffer[i] = UniChar.zero
}
let utf16text = text.utf16
let count = utf16text.count
if (count <= 0) {
return false
}
var i = 0
for c in utf16text {
buffer[i] = c
i += 1
}
return true
}
}