progrium / darwinkit

Native Mac APIs for Go. Previously known as MacDriver
MIT License
4.42k stars 147 forks source link

Add pointer to pointer support #244

Closed programmingkidx closed 5 months ago

programmingkidx commented 6 months ago

Pointer to pointer types are commonly used to provide a method a way to communicate information to calling methods. A popular example of this is to use NSError to communicate any error information. This pull request adds the ability for DarwinKit to correctly use pointer to pointer types so they can work.

This change uses a new rule where if a parameter is a pointer to a pointer type (e.g. NSError **error), the user is to use the Go wrapper for that type and wrap the variable with unsafe.Pointer(&theVariable).

So for a parameter like NSError **err, the Go code would declare an Error instance and then use that instance in the method like this: unsafe.Pointer(&err)

programmingkidx commented 6 months ago

To test this pull request I made several programs available.

This program loads a nib file and and displays it.

image

Directions: 1) Download the attached zip file and extract its nib file by double clicking on the file. MainMenu.nib.zip 2) Copy and paste the program below into a file called main.go. 3) Run the program by using this command: go run main.go.

// File: loadnib.go
// Date: 11/29/23
// Description: Uses a NIB file to display the interface
// Run directions: go run main.go

package main

import (
    "fmt"
    "os"
    "github.com/progrium/macdriver/macos/appkit"
    f "github.com/progrium/macdriver/macos/foundation"
    "github.com/progrium/macdriver/objc"
    "github.com/progrium/macdriver/helper/action"
    "unsafe"
)

var textField appkit.TextField

func main() {
    // Setup the application
    app := appkit.Application_SharedApplication()
    app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular)
    app.ActivateIgnoringOtherApps(true)

    // Get the NIB file's data
    godata, err := os.ReadFile("MainMenu.nib")
    if err != nil {
        fmt.Println("Failed to load nib file:", err)
        return
    }
    myNib := appkit.NewNibWithNibDataBundle(godata, nil)

    var myObjects = f.Array_Array()
    status := myNib.InstantiateWithOwnerTopLevelObjects(nil, unsafe.Pointer(&myObjects))
    if status == false {
        fmt.Println("Error: failed to instantiate nib file")
        return
    }

    // find the window and display it
    foundWindow := false
    var i uint
    for i = 0; i < myObjects.Count(); i++ {
        theObject := myObjects.ObjectAtIndex(i)
        if theObject.IsKindOfClass(objc.GetClass("NSWindow")) {
            mainWindow := appkit.WindowFrom(theObject.Ptr())
            mainWindow.OrderFront(nil)
            foundWindow = true
            aButton := mainWindow.ContentView().ViewWithTag(1)
            action.Set(appkit.ButtonFrom(aButton.Ptr()), doButton)

            textFieldView := mainWindow.ContentView().ViewWithTag(2)
            textField = appkit.TextFieldFrom(textFieldView.Ptr())
            break
        }
    }

    if foundWindow == false {
        fmt.Println("Error: Failed to find window in nib file")
        return
    }
    app.Run()
}

// called when the user pushes the button
func doButton(sender objc.Object) {
    fmt.Println("I'M ALIVE!!!")
    textField.SetStringValue("OUCH!!!")
}



This program test the use of (NS)Error. It will tell you if its tests succeed or fail. Directions: 1) Create a new file called main.go. 2) Copy and paste the code below into the file. 3) Run the program like this: go run main.go

The output should look like this: Testing with problem string...pass Testing with good string...pass

// Description: test generating a NSError using the XMLDocument class

package main

import "fmt"
import f "github.com/progrium/macdriver/macos/foundation"
import "unsafe"

func main() {
    testWithProblem()
    testWithoutProblem()
}

// Use a bad string to try to make a XML document. It should fail to create one.
func testWithProblem() {
    inputStr := "BUG<root><element>Some text</element></root>"
    options := f.XMLNodePreserveWhitespace
    var myError f.Error
    f.NewXMLDocumentWithXMLStringOptionsError(inputStr, options, unsafe.Pointer(&myError))
    if myError.IsNil() == false {
        fmt.Println("Testing with problem string...pass")
        //fmt.Printf("Error code: %d\t domain:%s\t  userInfo:%s\n", myError.Code, myError.Domain, myError.UserInfo)
    } else {
        fmt.Println("Testing with problem string...fail")
    }
}

// Use a good string to try to make a XML document. It should succeed
func testWithoutProblem() {
    inputStr := "<root><element>Some text</element></root>"
    options := f.XMLNodePreserveWhitespace
    var myError f.Error
    f.NewXMLDocumentWithXMLStringOptionsError(inputStr, options, unsafe.Pointer(&myError))
    if myError.IsNil() == true {
        fmt.Println("Testing with good string...pass")
    } else {
        fmt.Println("Testing with good string...fail")
    }
}
progrium commented 6 months ago

I think this could be a good general solution. @tmc thoughts?

programmingkidx commented 5 months ago

@tmc Any questions or comments?