PureBasic Forum
https://www.purebasic.fr/english/

Module OSX Helper for Observer and Methods
https://www.purebasic.fr/english/viewtopic.php?f=19&t=72642
Page 1 of 1

Author:  mk-soft [ Sat Apr 13, 2019 9:13 pm ]
Post subject:  Module OSX Helper for Observer and Methods

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:
;-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

Page 1 of 1 All times are UTC + 1 hour
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
http://www.phpbb.com/