Module OSX Helper for Observer and Methods

Mac OSX specific forum
User avatar
mk-soft
Always Here
Always Here
Posts: 5333
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Module OSX Helper for Observer and Methods

Post by mk-soft »

There are always nice examples of how to work with observer and methods.
I wanted to simplify that a bit.

I don't know which methods can be added to a gadget. Removing a single method is not possible without further ado.

Code: Select all

;-TOP
; Comment : osx Helper
; Author  : mk-soft
; Version : v1.01
; Create  : 13.04.2019
; Update  : 
; OS      : macOS

;- Begin Module

DeclareModule osxClassHelper
  ; Create a new class with NSObject and Method
  Declare CreateClass(ClassName.s, RegisterName.s, *Callback, Args.s) ; Result Object (Instance)
  Declare DisposeClass(ClassName.s)
  
  ; Add Method to Gadget
  Declare AddGadgetMethod(Gadget, RegisterName.s, *Callback, Args.s) ; True or False
  Declare DisposeGadgetMethods(Gadget)
  
EndDeclareModule

; ---

Module osxClassHelper
  
  EnableExplicit
  
  ImportC ""
    objc_disposeClassPair(*Class)
  EndImport
  
  ;-- Structure
  
  Structure udtClassObject
    *Class
    *Object
  EndStructure
  
  Structure udtMethodList
    Name.s
    *Method
  EndStructure
  
  Structure udtGadgetObject
    *GadgetID
    *OldGadgetClass
    *NewGadgetClass
    Map MethodList.udtMethodList()
  EndStructure
  
  ;-- Globals
  
  Global NewMap ClassObject.udtClassObject()
  Global NewMap GadgetObject.udtGadgetObject()
  
  ;- New Object with instance
  
  Procedure CreateClass(ClassName.s, RegisterName.s, *Callback, Args.s)
    Protected *Class, *Method, *Object
    *Class = objc_allocateClassPair_(objc_getClass_("NSObject"), ClassName, 0)
    If Not *Class
      ProcedureReturn 0
    EndIf
    *Method = class_addMethod_(*Class, sel_registerName_(RegisterName), *Callback, Args)
    If Not *Method
      objc_disposeClassPair(*Class)
      ProcedureReturn 0
    EndIf
    If Not objc_registerClassPair_(*Class)
      objc_disposeClassPair(*Class)
      ProcedureReturn 0
    EndIf
    *Object = CocoaMessage(0, 0, ClassName + " new")
    If Not *Object
      objc_disposeClassPair(*Class)
      ProcedureReturn 0
    EndIf
    ClassObject(ClassName)\Class = *Class
    ClassObject()\Object = *Object
    ProcedureReturn *Object
  EndProcedure
  
  Procedure DisposeClass(ClassName.s)
    If FindMapElement(ClassObject(), ClassName)
      CocoaMessage(0, ClassObject()\Object, "release")
      objc_disposeClassPair(ClassObject()\Class)
      DeleteMapElement(ClassObject())
    EndIf 
  EndProcedure
  
  ;- Add Method to Gadget
  
  Procedure AddGadgetMethod(Gadget, RegisterName.s, *Callback, Args.s)
    
    With GadgetObject()
      If Not FindMapElement(GadgetObject(), Hex(Gadget))
        AddMapElement(GadgetObject(), Hex(Gadget))
        \GadgetID = GadgetID(Gadget)
        \OldGadgetClass = CocoaMessage(0, GadgetID(Gadget), "class")
        \NewGadgetClass = objc_allocateClassPair_(\OldGadgetClass, "classGadget_" + \GadgetID, 0)
        If Not \NewGadgetClass
          ProcedureReturn 0
        EndIf
        objc_registerClassPair_(\NewGadgetClass)
        object_setClass_(\GadgetID, \NewGadgetClass)
      EndIf
      If Not FindMapElement(\MethodList(), RegisterName)
        AddMapElement(\MethodList(), RegisterName)
        \MethodList()\Name = RegisterName
        \MethodList()\Method = class_addMethod_(\NewGadgetClass, sel_registerName_(RegisterName), *Callback, Args)
        If Not \MethodList()\Method
          DeleteMapElement(\MethodList())
          If MapSize(\MethodList()) = 0
            object_setClass_(\GadgetID, \OldGadgetClass)
            objc_disposeClassPair(\NewGadgetClass)
            DeleteMapElement(GadgetObject())
          EndIf
          ProcedureReturn 0
        EndIf
      EndIf
      ProcedureReturn \MethodList()\Method
    EndWith
  EndProcedure
  
  Procedure DisposeGadgetMethods(Gadget)
    With GadgetObject()
      If FindMapElement(GadgetObject(), Hex(Gadget))
        object_setClass_(\GadgetID, \OldGadgetClass)
        objc_disposeClassPair(\NewGadgetClass)
        DeleteMapElement(GadgetObject())
      EndIf
    EndWith
  EndProcedure
  
EndModule

;- End Module

CompilerIf #PB_Compiler_IsMainFile
  
  UseModule osxClassHelper
  
  EnumerationBinary
    #NSKeyValueObservingOptionNew
    #NSKeyValueObservingOptionOld
  EndEnumeration
  
  Global *NSKeyValueChangeNewKey.Integer = dlsym_(#RTLD_DEFAULT, "NSKeyValueChangeNewKey")
  Global *NSKeyValueChangeOldKey.Integer = dlsym_(#RTLD_DEFAULT, "NSKeyValueChangeOldKey")
  
  Enumeration #PB_Event_FirstCustomValue
    #EventChangeAppearance
  EndEnumeration
  
  ProcedureC KVO(obj, sel, keyPath, object, change, context)
    Select PeekS(CocoaMessage(0, keyPath, "UTF8String"), -1, #PB_UTF8)
        
      Case "effectiveAppearance":
        CocoaMessage(0, 0, "NSAppearance setCurrentAppearance:", CocoaMessage(0, change, "objectForKey:", *NSKeyValueChangeNewKey\i))
        PostEvent(#EventChangeAppearance)
        
    EndSelect
  EndProcedure
  
  ; Create Key-Value Observer class (PB_KVO)
  KVO = CreateClass("PB_KVO", "observeValueForKeyPath:ofObject:change:context:", @KVO(), "v@:@@@^v") 
  
  ; add observer
  Global NSApp.i = CocoaMessage(0, 0, "NSApplication sharedApplication")
  CocoaMessage(0, NSApp, "addObserver:", KVO, "forKeyPath:$", @"effectiveAppearance", "options:", #NSKeyValueObservingOptionNew, "context:", #nil)
  
  ; ---
  
  Procedure NSColorToRGB(NSColor)
    Protected.cgfloat red, green, blue
    Protected r, g, b, a
    Protected nscolorspace, rgb
    nscolorspace = CocoaMessage(0, nscolor, "colorUsingColorSpaceName:$", @"NSCalibratedRGBColorSpace")
    If nscolorspace
      CocoaMessage(@red, nscolorspace, "redComponent")
      CocoaMessage(@green, nscolorspace, "greenComponent")
      CocoaMessage(@blue, nscolorspace, "blueComponent")
      rgb = RGB(red * 255.0, green * 255.0, blue * 255.0)
      ProcedureReturn rgb
    EndIf
  EndProcedure
  
  Procedure NSColorByNameToRGB(NSColorName.s)
    Protected.cgfloat red, green, blue
    Protected r, g, b, a
    Protected nscolorspace, rgb
    nscolorspace = CocoaMessage(0, CocoaMessage(0, 0, "NSColor " + NSColorName), "colorUsingColorSpaceName:$", @"NSCalibratedRGBColorSpace")
    If nscolorspace
      CocoaMessage(@red, nscolorspace, "redComponent")
      CocoaMessage(@green, nscolorspace, "greenComponent")
      CocoaMessage(@blue, nscolorspace, "blueComponent")
      rgb = RGB(red * 255.0, green * 255.0, blue * 255.0)
      ProcedureReturn rgb
    EndIf
  EndProcedure
  ; ---
  
  Global Event, text_color, control_background_color , buttonimage, buttonimage_pressed 
  
  Procedure UpdateColors()
    text_color = NSColorByNameToRGB("textColor")
    control_background_color = NSColorByNameToRGB("windowBackgroundColor")
    If StartDrawing(CanvasOutput(0))
      Box(0, 0, OutputWidth(), OutputHeight(), control_background_color)
      DrawingMode(#PB_2DDrawing_Transparent)
      DrawText(10, 10, "Update colors", text_color)
      StopDrawing()
    EndIf 
  EndProcedure
  
  ; ---
  
  ProcedureC UpdateLayer(SubclassedButton.I, Selector.I)
    Protected cell.i, layer.i, image.i
    Protected Rect.CGRect
    
    cell = CocoaMessage(0, SubclassedButton, "cell")
    layer = CocoaMessage(0, SubclassedButton, "layer", 0 )
    If CocoaMessage(0, cell, "isHighlighted")
      ;image =CocoaMessage(0, 0, "NSImage imageNamed:$", @"button_pressed.png")
      CocoaMessage(0, layer, "setContents:", buttonimage_pressed)
    Else
      ;image =CocoaMessage(0, 0, "NSImage imageNamed:$", @"button.png")
      CocoaMessage(0, layer, "setContents:", buttonimage)
      CocoaMessage(@Rect, layer, "contentsCenter")
      Rect\origin\x  =0.5
      Rect\origin\y =0.5
      Rect\size\width =0
      Rect\size\height =0
      CocoaMessage(0, layer, "setContentsCenter:@", @Rect)
    EndIf
  EndProcedure
  
  ; ---
  
  buttonimage = CreateImage(0, 140, 30, 32, $FF901E)
  buttonimage_pressed = CreateImage(1, 140, 30, 32, $CD7418)
  
  OpenWindow(0, #PB_Ignore, #PB_Ignore, 300, 150, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  
  CanvasGadget(0, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 20, #PB_Canvas_Container)
  ButtonGadget(1, 70, 40, 140, 30, "Click me!")
  SetGadgetFont(1, LoadFont(0, "Apple Chancery", 18))
  CloseGadgetList()
  
  CocoaMessage(0, GadgetID(1), "setWantsLayer:", #YES)
  AddGadgetMethod(1, "updateLayer", @UpdateLayer(), "v@")
  
  UpdateColors()
  
  Repeat
    Event = WaitWindowEvent()
    If Event = #EventChangeAppearance
      UpdateColors()
    EndIf
  Until Event = #PB_Event_CloseWindow
  
  ; remove observer
  
  CocoaMessage(0, NSApp, "removeObserver:", KVO, "forKeyPath:$", @"effectiveAppearance")
  DisposeClass("PB_KVO")
  DisposeGadgetMethods(1)
  
CompilerEndIf
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive