mirror of
https://github.com/taigrr/wails.git
synced 2026-04-04 14:12:40 -07:00
Compare commits
3 Commits
v2.0.0-alp
...
feature/v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
480dcb7895 | ||
|
|
c8d89cf002 | ||
|
|
fc669ede37 |
@@ -26,7 +26,7 @@ export function OpenURL(url) {
|
||||
* Opens the given filename using the system's default file handler
|
||||
*
|
||||
* @export
|
||||
* @param {string} filename
|
||||
* @param {sting} filename
|
||||
* @returns
|
||||
*/
|
||||
export function OpenFile(filename) {
|
||||
|
||||
@@ -62,7 +62,7 @@ if (window.crypto) {
|
||||
export function Call(bindingName, data, timeout) {
|
||||
|
||||
// Timeout infinite by default
|
||||
if (timeout == null) {
|
||||
if (timeout == null || timeout == undefined) {
|
||||
timeout = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ function Invoke(message) {
|
||||
*
|
||||
* @export
|
||||
* @param {string} type
|
||||
* @param {Object} payload
|
||||
* @param {string} payload
|
||||
* @param {string=} callbackID
|
||||
*/
|
||||
export function SendMessage(type, payload, callbackID) {
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/shell"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/github"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
)
|
||||
|
||||
// AddSubcommand adds the `init` command for the Wails application
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer, currentVersion string) error {
|
||||
|
||||
command := app.NewSubCommand("update", "Update the Wails CLI")
|
||||
command.LongDescription(`This command allows you to update your version of Wails.`)
|
||||
|
||||
// Setup flags
|
||||
var prereleaseRequired bool
|
||||
command.BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired)
|
||||
|
||||
var specificVersion string
|
||||
command.StringFlag("version", "Install a specific version (Overrides other flags)", &specificVersion)
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
// Create logger
|
||||
logger := clilogger.New(w)
|
||||
|
||||
// Print banner
|
||||
app.PrintBanner()
|
||||
logger.Println("Checking for updates...")
|
||||
|
||||
var desiredVersion *github.SemanticVersion
|
||||
var err error
|
||||
var valid bool
|
||||
|
||||
if len(specificVersion) > 0 {
|
||||
// Check if this is a valid version
|
||||
valid, err = github.IsValidTag(specificVersion)
|
||||
if err == nil {
|
||||
if !valid {
|
||||
err = fmt.Errorf("version '%s' is invalid", specificVersion)
|
||||
} else {
|
||||
desiredVersion, err = github.NewSemanticVersion(specificVersion)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if prereleaseRequired {
|
||||
desiredVersion, err = github.GetLatestPreRelease()
|
||||
} else {
|
||||
desiredVersion, err = github.GetLatestStableRelease()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println(" Current Version : " + currentVersion)
|
||||
|
||||
if len(specificVersion) > 0 {
|
||||
fmt.Printf(" Desired Version : v%s\n", desiredVersion)
|
||||
} else {
|
||||
if prereleaseRequired {
|
||||
fmt.Printf(" Latest Prerelease : v%s\n", desiredVersion)
|
||||
} else {
|
||||
fmt.Printf(" Latest Release : v%s\n", desiredVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return updateToVersion(logger, desiredVersion, len(specificVersion) > 0, currentVersion)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.SemanticVersion, force bool, currentVersion string) error {
|
||||
|
||||
var targetVersionString = "v" + targetVersion.String()
|
||||
|
||||
// Early exit
|
||||
if targetVersionString == currentVersion {
|
||||
logger.Println("Looks like you're up to date!")
|
||||
return nil
|
||||
}
|
||||
|
||||
var desiredVersion string
|
||||
|
||||
if !force {
|
||||
|
||||
compareVersion := currentVersion
|
||||
|
||||
currentVersion, err := github.NewSemanticVersion(compareVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var success bool
|
||||
|
||||
// Release -> Pre-Release = Massage current version to prerelease format
|
||||
if targetVersion.IsPreRelease() && currentVersion.IsRelease() {
|
||||
testVersion, err := github.NewSemanticVersion(compareVersion + "-0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
success, _ = targetVersion.IsGreaterThan(testVersion)
|
||||
}
|
||||
// Pre-Release -> Release = Massage target version to prerelease format
|
||||
if targetVersion.IsRelease() && currentVersion.IsPreRelease() {
|
||||
// We are ok with greater than or equal
|
||||
mainversion := currentVersion.MainVersion()
|
||||
targetVersion, err = github.NewSemanticVersion(targetVersion.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
success, _ = targetVersion.IsGreaterThanOrEqual(mainversion)
|
||||
}
|
||||
|
||||
// Release -> Release = Standard check
|
||||
if (targetVersion.IsRelease() && currentVersion.IsRelease()) ||
|
||||
(targetVersion.IsPreRelease() && currentVersion.IsPreRelease()) {
|
||||
|
||||
success, _ = targetVersion.IsGreaterThan(currentVersion)
|
||||
}
|
||||
|
||||
// Compare
|
||||
if !success {
|
||||
logger.Println("Error: The requested version is lower than the current version.")
|
||||
logger.Println("If this is what you really want to do, use `wails update -version %s`", targetVersionString)
|
||||
return nil
|
||||
}
|
||||
|
||||
desiredVersion = "v" + targetVersion.String()
|
||||
|
||||
} else {
|
||||
desiredVersion = "v" + targetVersion.String()
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
logger.Print("Installing Wails " + desiredVersion + "...")
|
||||
|
||||
// Run command in non module directory
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Fatal("Cannot find home directory! Please file a bug report!")
|
||||
}
|
||||
|
||||
sout, serr, err := shell.RunCommand(homeDir, "go", "get", "github.com/wailsapp/wails/v2/cmd/wails@"+desiredVersion)
|
||||
if err != nil {
|
||||
logger.Println("Failed.")
|
||||
logger.Println(sout + `\n` + serr)
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
logger.Println("Wails updated to " + desiredVersion)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,8 +3,6 @@ package main
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
|
||||
@@ -50,11 +48,6 @@ func main() {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
err = update.AddSubcommand(app, os.Stdout, version)
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
println("\n\nERROR: " + err.Error())
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v2.0.0-alpha.19"
|
||||
var version = "v2.0.0-alpha.10"
|
||||
|
||||
@@ -3,7 +3,6 @@ module github.com/wailsapp/wails/v2
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fatih/structtag v1.2.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
||||
@@ -2,39 +2,11 @@
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Init initialises the application for a debug environment
|
||||
func (a *App) Init() error {
|
||||
// Indicate debug mode
|
||||
a.debug = true
|
||||
|
||||
if a.appType == "desktop" {
|
||||
// Enable dev tools
|
||||
a.options.DevTools = true
|
||||
}
|
||||
|
||||
// Set log levels
|
||||
greeting := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
|
||||
flag.Parse()
|
||||
if len(*greeting) > 0 {
|
||||
switch strings.ToLower(*greeting) {
|
||||
case "trace":
|
||||
a.logger.SetLogLevel(logger.TRACE)
|
||||
case "info":
|
||||
a.logger.SetLogLevel(logger.INFO)
|
||||
case "warning":
|
||||
a.logger.SetLogLevel(logger.WARNING)
|
||||
case "error":
|
||||
a.logger.SetLogLevel(logger.ERROR)
|
||||
default:
|
||||
a.logger.SetLogLevel(logger.DEBUG)
|
||||
}
|
||||
}
|
||||
|
||||
// Enable dev tools
|
||||
a.options.DevTools = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,9 +8,8 @@ package app
|
||||
// will be unknown and the application will not work as expected.
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
@@ -39,3 +38,7 @@ func (a *App) Run() error {
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bind the dummy interface
|
||||
func (a *App) Bind(_ interface{}) {
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ import (
|
||||
|
||||
// App defines a Wails application structure
|
||||
type App struct {
|
||||
appType string
|
||||
|
||||
window *ffenestri.Application
|
||||
servicebus *servicebus.ServiceBus
|
||||
logger *logger.Logger
|
||||
@@ -81,15 +79,11 @@ func CreateApp(appoptions *options.App) (*App, error) {
|
||||
|
||||
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
|
||||
|
||||
// Create binding exemptions - Ugly hack. There must be a better way
|
||||
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
|
||||
|
||||
result := &App{
|
||||
appType: "desktop",
|
||||
window: window,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
logger: myLogger,
|
||||
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
menuManager: menuManager,
|
||||
startupCallback: appoptions.Startup,
|
||||
shutdownCallback: appoptions.Shutdown,
|
||||
@@ -219,3 +213,14 @@ func (a *App) Run() error {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Bind a struct to the application by passing in
|
||||
// a pointer to it
|
||||
func (a *App) Bind(structPtr interface{}) {
|
||||
|
||||
// Add the struct to the bindings
|
||||
err := a.bindings.Add(structPtr)
|
||||
if err != nil {
|
||||
a.logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func CreateApp(options *Options) *App {
|
||||
webserver: webserver.NewWebServer(myLogger),
|
||||
servicebus: servicebus.New(myLogger),
|
||||
logger: myLogger,
|
||||
bindings: binding.NewBindings(myLogger, options.Bind),
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
}
|
||||
|
||||
// Initialise the app
|
||||
@@ -192,3 +192,14 @@ func (a *App) Run() error {
|
||||
|
||||
return cli.Run()
|
||||
}
|
||||
|
||||
// Bind a struct to the application by passing in
|
||||
// a pointer to it
|
||||
func (a *App) Bind(structPtr interface{}) {
|
||||
|
||||
// Add the struct to the bindings
|
||||
err := a.bindings.Add(structPtr)
|
||||
if err != nil {
|
||||
a.logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,10 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/internal/subsystem"
|
||||
"github.com/wailsapp/wails/v2/internal/webserver"
|
||||
@@ -20,16 +17,12 @@ import (
|
||||
|
||||
// App defines a Wails application structure
|
||||
type App struct {
|
||||
appType string
|
||||
|
||||
binding *subsystem.Binding
|
||||
call *subsystem.Call
|
||||
event *subsystem.Event
|
||||
log *subsystem.Log
|
||||
runtime *subsystem.Runtime
|
||||
|
||||
options *options.App
|
||||
|
||||
bindings *binding.Bindings
|
||||
logger *logger.Logger
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
@@ -37,40 +30,28 @@ type App struct {
|
||||
webserver *webserver.WebServer
|
||||
|
||||
debug bool
|
||||
|
||||
// Application Stores
|
||||
loglevelStore *runtime.Store
|
||||
appconfigStore *runtime.Store
|
||||
|
||||
// Startup/Shutdown
|
||||
startupCallback func(*runtime.Runtime)
|
||||
shutdownCallback func()
|
||||
}
|
||||
|
||||
// Create App
|
||||
func CreateApp(appoptions *options.App) (*App, error) {
|
||||
func CreateApp(options *Options) *App {
|
||||
options.mergeDefaults()
|
||||
// We ignore the inputs (for now)
|
||||
|
||||
// Merge default options
|
||||
options.MergeDefaults(appoptions)
|
||||
|
||||
// Set up logger
|
||||
myLogger := logger.New(appoptions.Logger)
|
||||
myLogger.SetLogLevel(appoptions.LogLevel)
|
||||
// TODO: Allow logger output override on CLI
|
||||
myLogger := logger.New(os.Stdout)
|
||||
myLogger.SetLogLevel(logger.TRACE)
|
||||
|
||||
result := &App{
|
||||
appType: "server",
|
||||
bindings: binding.NewBindings(myLogger, options.Bind),
|
||||
logger: myLogger,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
webserver: webserver.NewWebServer(myLogger),
|
||||
startupCallback: appoptions.Startup,
|
||||
shutdownCallback: appoptions.Shutdown,
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
logger: myLogger,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
webserver: webserver.NewWebServer(myLogger),
|
||||
}
|
||||
|
||||
// Initialise app
|
||||
result.Init()
|
||||
|
||||
return result, nil
|
||||
return result
|
||||
}
|
||||
|
||||
// Run the application
|
||||
@@ -107,21 +88,8 @@ func (a *App) Run() error {
|
||||
if debugMode {
|
||||
a.servicebus.Debug()
|
||||
}
|
||||
|
||||
// Start the runtime
|
||||
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.runtime = runtime
|
||||
a.runtime.Start()
|
||||
|
||||
// Application Stores
|
||||
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
|
||||
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
|
||||
|
||||
a.servicebus.Start()
|
||||
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
|
||||
log, err := subsystem.NewLog(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -134,6 +102,14 @@ func (a *App) Run() error {
|
||||
a.dispatcher = dispatcher
|
||||
a.dispatcher.Start()
|
||||
|
||||
// Start the runtime
|
||||
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.runtime = runtime
|
||||
a.runtime.Start()
|
||||
|
||||
// Start the binding subsystem
|
||||
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
|
||||
if err != nil {
|
||||
@@ -151,7 +127,7 @@ func (a *App) Run() error {
|
||||
a.event.Start()
|
||||
|
||||
// Start the call subsystem
|
||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -171,3 +147,14 @@ func (a *App) Run() error {
|
||||
|
||||
return cli.Run()
|
||||
}
|
||||
|
||||
// Bind a struct to the application by passing in
|
||||
// a pointer to it
|
||||
func (a *App) Bind(structPtr interface{}) {
|
||||
|
||||
// Add the struct to the bindings
|
||||
err := a.bindings.Add(structPtr)
|
||||
if err != nil {
|
||||
a.logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,53 +2,28 @@ package binding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
|
||||
type Bindings struct {
|
||||
db *DB
|
||||
logger logger.CustomLogger
|
||||
exemptions slicer.StringSlicer
|
||||
db *DB
|
||||
logger logger.CustomLogger
|
||||
}
|
||||
|
||||
// NewBindings returns a new Bindings object
|
||||
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings {
|
||||
result := &Bindings{
|
||||
func NewBindings(logger *logger.Logger) *Bindings {
|
||||
return &Bindings{
|
||||
db: newDB(),
|
||||
logger: logger.CustomLogger("Bindings"),
|
||||
}
|
||||
|
||||
for _, exemption := range exemptions {
|
||||
if exemptions == nil {
|
||||
continue
|
||||
}
|
||||
name := runtime.FuncForPC(reflect.ValueOf(exemption).Pointer()).Name()
|
||||
// Yuk yuk yuk! Is there a better way?
|
||||
name = strings.TrimSuffix(name, "-fm")
|
||||
result.exemptions.Add(name)
|
||||
}
|
||||
|
||||
// Add the structs to bind
|
||||
for _, ptr := range structPointersToBind {
|
||||
err := result.Add(ptr)
|
||||
if err != nil {
|
||||
logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Add the given struct methods to the Bindings
|
||||
func (b *Bindings) Add(structPtr interface{}) error {
|
||||
|
||||
methods, err := b.getMethods(structPtr)
|
||||
methods, err := getMethods(structPtr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot bind value to app: %s", err.Error())
|
||||
}
|
||||
@@ -61,6 +36,7 @@ func (b *Bindings) Add(structPtr interface{}) error {
|
||||
|
||||
// Add it as a regular method
|
||||
b.db.AddMethod(packageName, structName, methodName, method)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,9 +30,6 @@ func (b *BoundMethod) OutputCount() int {
|
||||
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
|
||||
|
||||
result := make([]interface{}, b.InputCount())
|
||||
if len(args) != b.InputCount() {
|
||||
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount())
|
||||
}
|
||||
for index, arg := range args {
|
||||
typ := b.Inputs[index].reflectType
|
||||
inputValue := reflect.New(typ).Interface()
|
||||
|
||||
@@ -23,7 +23,7 @@ func isStruct(value interface{}) bool {
|
||||
return reflect.ValueOf(value).Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
func getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
|
||||
// Create result placeholder
|
||||
var result []*BoundMethod
|
||||
@@ -56,11 +56,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
fullMethodName := baseName + "." + methodName
|
||||
method := structValue.MethodByName(methodName)
|
||||
|
||||
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
|
||||
if b.exemptions.Contains(methodReflectName) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create new method
|
||||
boundMethod := &BoundMethod{
|
||||
Name: fullMethodName,
|
||||
|
||||
40
v2/internal/counter/counter.go
Normal file
40
v2/internal/counter/counter.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package counter
|
||||
|
||||
import "sync"
|
||||
|
||||
type Counter struct {
|
||||
initialValue uint64
|
||||
value uint64
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewCounter(initialValue uint64) *Counter {
|
||||
return &Counter{
|
||||
initialValue: initialValue,
|
||||
value: initialValue,
|
||||
}
|
||||
}
|
||||
|
||||
// SetValue sets the value to the given value
|
||||
func (c *Counter) SetValue(value uint64) {
|
||||
c.lock.Lock()
|
||||
c.value = value
|
||||
c.lock.Unlock()
|
||||
}
|
||||
|
||||
// Increment adds 1 to the counter and returns its value
|
||||
func (c *Counter) Increment() uint64 {
|
||||
var result uint64
|
||||
c.lock.Lock()
|
||||
c.value++
|
||||
result = c.value
|
||||
c.lock.Unlock()
|
||||
return result
|
||||
}
|
||||
|
||||
// Reset the value to the initial value
|
||||
func (c *Counter) Reset() {
|
||||
c.lock.Lock()
|
||||
c.value = c.initialValue
|
||||
c.lock.Unlock()
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Joel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,125 +0,0 @@
|
||||
// deepcopy makes deep copies of things. A standard copy will copy the
|
||||
// pointers: deep copy copies the values pointed to. Unexported field
|
||||
// values are not copied.
|
||||
//
|
||||
// Copyright (c)2014-2016, Joel Scoble (github.com/mohae), all rights reserved.
|
||||
// License: MIT, for more details check the included LICENSE file.
|
||||
package deepcopy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Interface for delegating copy process to type
|
||||
type Interface interface {
|
||||
DeepCopy() interface{}
|
||||
}
|
||||
|
||||
// Iface is an alias to Copy; this exists for backwards compatibility reasons.
|
||||
func Iface(iface interface{}) interface{} {
|
||||
return Copy(iface)
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of whatever is passed to it and returns the copy
|
||||
// in an interface{}. The returned value will need to be asserted to the
|
||||
// correct type.
|
||||
func Copy(src interface{}) interface{} {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make the interface a reflect.Value
|
||||
original := reflect.ValueOf(src)
|
||||
|
||||
// Make a copy of the same type as the original.
|
||||
cpy := reflect.New(original.Type()).Elem()
|
||||
|
||||
// Recursively copy the original.
|
||||
copyRecursive(original, cpy)
|
||||
|
||||
// Return the copy as an interface.
|
||||
return cpy.Interface()
|
||||
}
|
||||
|
||||
// copyRecursive does the actual copying of the interface. It currently has
|
||||
// limited support for what it can handle. Add as needed.
|
||||
func copyRecursive(original, cpy reflect.Value) {
|
||||
// check for implement deepcopy.Interface
|
||||
if original.CanInterface() {
|
||||
if copier, ok := original.Interface().(Interface); ok {
|
||||
cpy.Set(reflect.ValueOf(copier.DeepCopy()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handle according to original's Kind
|
||||
switch original.Kind() {
|
||||
case reflect.Ptr:
|
||||
// Get the actual value being pointed to.
|
||||
originalValue := original.Elem()
|
||||
|
||||
// if it isn't valid, return.
|
||||
if !originalValue.IsValid() {
|
||||
return
|
||||
}
|
||||
cpy.Set(reflect.New(originalValue.Type()))
|
||||
copyRecursive(originalValue, cpy.Elem())
|
||||
|
||||
case reflect.Interface:
|
||||
// If this is a nil, don't do anything
|
||||
if original.IsNil() {
|
||||
return
|
||||
}
|
||||
// Get the value for the interface, not the pointer.
|
||||
originalValue := original.Elem()
|
||||
|
||||
// Get the value by calling Elem().
|
||||
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||
copyRecursive(originalValue, copyValue)
|
||||
cpy.Set(copyValue)
|
||||
|
||||
case reflect.Struct:
|
||||
t, ok := original.Interface().(time.Time)
|
||||
if ok {
|
||||
cpy.Set(reflect.ValueOf(t))
|
||||
return
|
||||
}
|
||||
// Go through each field of the struct and copy it.
|
||||
for i := 0; i < original.NumField(); i++ {
|
||||
// The Type's StructField for a given field is checked to see if StructField.PkgPath
|
||||
// is set to determine if the field is exported or not because CanSet() returns false
|
||||
// for settable fields. I'm not sure why. -mohae
|
||||
if original.Type().Field(i).PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
copyRecursive(original.Field(i), cpy.Field(i))
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
if original.IsNil() {
|
||||
return
|
||||
}
|
||||
// Make a new slice and copy each element.
|
||||
cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))
|
||||
for i := 0; i < original.Len(); i++ {
|
||||
copyRecursive(original.Index(i), cpy.Index(i))
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
if original.IsNil() {
|
||||
return
|
||||
}
|
||||
cpy.Set(reflect.MakeMap(original.Type()))
|
||||
for _, key := range original.MapKeys() {
|
||||
originalValue := original.MapIndex(key)
|
||||
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||
copyRecursive(originalValue, copyValue)
|
||||
copyKey := Copy(key.Interface())
|
||||
cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue)
|
||||
}
|
||||
|
||||
default:
|
||||
cpy.Set(original)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,4 +10,8 @@ License: http://git.ozlabs.org/?p=ccan;a=blob;f=licenses/BSD-MIT;h=89de354795ec7
|
||||
|
||||
## hashmap
|
||||
Homepage: https://github.com/sheredom/hashmap.h
|
||||
License: https://github.com/sheredom/hashmap.h/blob/master/LICENSE
|
||||
License: https://github.com/sheredom/hashmap.h/blob/master/LICENSE
|
||||
|
||||
## utf8.h
|
||||
Homepage: https://github.com/sheredom/utf8.h
|
||||
License: https://github.com/sheredom/utf8.h/blob/master/LICENSE
|
||||
@@ -38,21 +38,31 @@ int freeHashmapItem(void *const context, struct hashmap_element_s *const e) {
|
||||
const char* getJSONString(JsonNode *item, const char* key) {
|
||||
// Get key
|
||||
JsonNode *node = json_find_member(item, key);
|
||||
const char *result = "";
|
||||
const char *result = NULL;
|
||||
if ( node != NULL && node->tag == JSON_STRING) {
|
||||
result = node->string_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* getJSONStringDefault(JsonNode *item, const char* key, const char* defaultValue) {
|
||||
const char* result = getJSONString(item, key);
|
||||
if ( result == NULL ) result = defaultValue;
|
||||
return result;
|
||||
}
|
||||
|
||||
void ABORT_JSON(JsonNode *node, const char* key) {
|
||||
ABORT("Unable to read required key '%s' from JSON: %s\n", key, json_encode(node));
|
||||
}
|
||||
|
||||
const char* mustJSONString(JsonNode *node, const char* key) {
|
||||
const char* result = getJSONString(node, key);
|
||||
if ( result == NULL ) {
|
||||
ABORT_JSON(node, key);
|
||||
const char* mustJSONString(JsonNode *item, const char* key) {
|
||||
JsonNode *member = json_find_member(item, key);
|
||||
if ( member == NULL ) {
|
||||
ABORT_JSON(item, key);
|
||||
}
|
||||
const char *result = "";
|
||||
if ( member != NULL && member->tag == JSON_STRING) {
|
||||
result = member->string_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -68,15 +78,23 @@ JsonNode* getJSONObject(JsonNode* node, const char* key) {
|
||||
return json_find_member(node, key);
|
||||
}
|
||||
|
||||
bool getJSONBool(JsonNode *item, const char* key, bool *result) {
|
||||
JsonNode *node = json_find_member(item, key);
|
||||
if ( node != NULL && node->tag == JSON_BOOL) {
|
||||
*result = node->bool_;
|
||||
return true;
|
||||
bool getJSONBool(JsonNode *node, const char* key) {
|
||||
JsonNode *result = json_find_member(node, key);
|
||||
if ( result != NULL && result->tag == JSON_BOOL) {
|
||||
return result->bool_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int mustJSONInt(JsonNode *node, const char* key) {
|
||||
JsonNode *result = json_find_member(node, key);
|
||||
if ( result == NULL || result->tag != JSON_NUMBER) {
|
||||
ABORT_JSON(result, key);
|
||||
}
|
||||
|
||||
return (int) result->number_;
|
||||
}
|
||||
|
||||
bool getJSONInt(JsonNode *item, const char* key, int *result) {
|
||||
JsonNode *node = json_find_member(item, key);
|
||||
if ( node != NULL && node->tag == JSON_NUMBER) {
|
||||
|
||||
@@ -18,23 +18,45 @@
|
||||
|
||||
#define STREQ(a,b) strcmp(a, b) == 0
|
||||
#define STREMPTY(string) strlen(string) == 0
|
||||
#define STRCOPY(a) concat(a, "")
|
||||
#define STRCOPY(str) (str == NULL ? NULL : concat(str, ""))
|
||||
#define STR_HAS_CHARS(input) input != NULL && strlen(input) > 0
|
||||
#define MEMFREE(input) free((void*)input); input = NULL;
|
||||
#define MEMFREE(input) if(input != NULL) { free((void*)input); input = NULL; }
|
||||
#define FREE_AND_SET(variable, value) if( variable != NULL ) { MEMFREE(variable); } variable = value
|
||||
#define NEW(struct) malloc(sizeof(struct));
|
||||
|
||||
#define HASHMAP_INIT(hashmap, initialSize, name) \
|
||||
if( 0 != hashmap_create((const unsigned)initialSize, &hashmap)) { \
|
||||
ABORT("Not enough memory to allocate %s!\n", name); \
|
||||
}
|
||||
|
||||
#define HASHMAP_PUT(hashmap, key, value) hashmap_put(&hashmap, key, strlen(key), value);
|
||||
#define HASHMAP_GET(hashmap, key) hashmap_get(&hashmap, key, strlen(key));
|
||||
#define HASHMAP_DESTROY(hashmap) hashmap_destroy(&hashmap);
|
||||
#define HASHMAP_SIZE(hashmap) hashmap_num_entries(&hashmap)
|
||||
#define HASHMAP_ITERATE(hashmap, function, context) if( hashmap_num_entries(&hashmap) > 0 ) { \
|
||||
if (0!=hashmap_iterate_pairs(&hashmap, function, context)) { \
|
||||
ABORT("failed to iterate hashmap entries!"); \
|
||||
} \
|
||||
}
|
||||
#define JSON_ADD_STRING(jsonObject, key, value) if( value != NULL ) { json_append_member(jsonObject, (char*)key, json_mkstring(value)); }
|
||||
#define JSON_ADD_NUMBER(jsonObject, key, value) json_append_member(jsonObject, (char*)key, json_mknumber(value));
|
||||
#define JSON_ADD_OBJECT(jsonObject, key, value) if( value != NULL ) { json_append_member(jsonObject, (char*)key, value); }
|
||||
#define JSON_ADD_BOOL(jsonObject, key, value) json_append_member(jsonObject, (char*)key, json_mkbool(value));
|
||||
#define JSON_ADD_ELEMENT(jsonObject, value) json_append_element(jsonObject, value);
|
||||
|
||||
// Credit: https://stackoverflow.com/a/8465083
|
||||
char* concat(const char *string1, const char *string2);
|
||||
void ABORT(const char *message, ...);
|
||||
int freeHashmapItem(void *const context, struct hashmap_element_s *const e);
|
||||
int freeHashmapItem(void *context, struct hashmap_element_s *e);
|
||||
const char* getJSONString(JsonNode *item, const char* key);
|
||||
const char* getJSONStringDefault(JsonNode *item, const char* key, const char* defaultValue);
|
||||
const char* mustJSONString(JsonNode *node, const char* key);
|
||||
JsonNode* getJSONObject(JsonNode* node, const char* key);
|
||||
JsonNode* mustJSONObject(JsonNode *node, const char* key);
|
||||
|
||||
bool getJSONBool(JsonNode *item, const char* key, bool *result);
|
||||
bool getJSONBool(JsonNode *item, const char* key);
|
||||
bool getJSONInt(JsonNode *item, const char* key, int *result);
|
||||
|
||||
int mustJSONInt(JsonNode *node, const char* key);
|
||||
JsonNode* mustParseJSON(const char* JSON);
|
||||
|
||||
#endif //ASSETS_C_COMMON_H
|
||||
|
||||
@@ -1,99 +1,99 @@
|
||||
////
|
||||
//// Created by Lea Anthony on 6/1/21.
|
||||
//////
|
||||
////// Created by Lea Anthony on 6/1/21.
|
||||
//////
|
||||
////
|
||||
//
|
||||
|
||||
#include "ffenestri_darwin.h"
|
||||
#include "common.h"
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "menu_darwin.h"
|
||||
|
||||
ContextMenu* NewContextMenu(const char* contextMenuJSON) {
|
||||
ContextMenu* result = malloc(sizeof(ContextMenu));
|
||||
|
||||
JsonNode* processedJSON = json_decode(contextMenuJSON);
|
||||
if( processedJSON == NULL ) {
|
||||
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", contextMenuJSON);
|
||||
}
|
||||
// Save reference to this json
|
||||
result->processedJSON = processedJSON;
|
||||
|
||||
result->ID = mustJSONString(processedJSON, "ID");
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
|
||||
result->menu = NewMenu(processedMenu);
|
||||
result->nsmenu = NULL;
|
||||
result->menu->menuType = ContextMenuType;
|
||||
result->menu->parentData = result;
|
||||
result->contextMenuData = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) {
|
||||
return (ContextMenu*)hashmap_get(&store->contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
|
||||
}
|
||||
|
||||
void DeleteContextMenu(ContextMenu* contextMenu) {
|
||||
// Free Menu
|
||||
DeleteMenu(contextMenu->menu);
|
||||
|
||||
// Delete any context menu data we may have stored
|
||||
if( contextMenu->contextMenuData != NULL ) {
|
||||
MEMFREE(contextMenu->contextMenuData);
|
||||
}
|
||||
|
||||
// Free JSON
|
||||
if (contextMenu->processedJSON != NULL ) {
|
||||
json_delete(contextMenu->processedJSON);
|
||||
contextMenu->processedJSON = NULL;
|
||||
}
|
||||
|
||||
// Free context menu
|
||||
free(contextMenu);
|
||||
}
|
||||
|
||||
int freeContextMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
DeleteContextMenu(e->data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData) {
|
||||
|
||||
// If no context menu ID was given, abort
|
||||
if( contextMenuID == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContextMenu* contextMenu = GetContextMenuByID(store, contextMenuID);
|
||||
|
||||
// We don't need the ID now
|
||||
MEMFREE(contextMenuID);
|
||||
|
||||
if( contextMenu == NULL ) {
|
||||
// Free context menu data
|
||||
if( contextMenuData != NULL ) {
|
||||
MEMFREE(contextMenuData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to store the context menu data. Free existing data if we have it
|
||||
// and set to the new value.
|
||||
FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
|
||||
|
||||
// Grab the content view and show the menu
|
||||
id contentView = msg(mainWindow, s("contentView"));
|
||||
|
||||
// Get the triggering event
|
||||
id menuEvent = msg(mainWindow, s("currentEvent"));
|
||||
|
||||
if( contextMenu->nsmenu == NULL ) {
|
||||
// GetMenu creates the NSMenu
|
||||
contextMenu->nsmenu = GetMenu(contextMenu->menu);
|
||||
}
|
||||
|
||||
// Show popup
|
||||
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
|
||||
|
||||
}
|
||||
|
||||
//#include "ffenestri_darwin.h"
|
||||
//#include "common.h"
|
||||
//#include "contextmenus_darwin.h"
|
||||
//#include "menu_darwin.h"
|
||||
//
|
||||
//ContextMenu* NewContextMenu(const char* contextMenuJSON, struct TrayMenuStore *store) {
|
||||
// ContextMenu* result = malloc(sizeof(ContextMenu));
|
||||
//
|
||||
// JsonNode* processedJSON = json_decode(contextMenuJSON);
|
||||
// if( processedJSON == NULL ) {
|
||||
// ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", contextMenuJSON);
|
||||
// }
|
||||
// // Save reference to this json
|
||||
// result->processedJSON = processedJSON;
|
||||
//
|
||||
// result->ID = mustJSONString(processedJSON, "ID");
|
||||
// JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
//
|
||||
//// result->menu = NewMenu(processedMenu);
|
||||
// result->nsmenu = NULL;
|
||||
// result->menu->menuType = ContextMenuType;
|
||||
// result->menu->store = store;
|
||||
// result->contextMenuData = NULL;
|
||||
// return result;
|
||||
//}
|
||||
//
|
||||
//ContextMenu* GetContextMenuByID(ContextMenuStore* store, const char *contextMenuID) {
|
||||
// return (ContextMenu*)hashmap_get(&store->contextMenuMap, (char*)contextMenuID, strlen(contextMenuID));
|
||||
//}
|
||||
//
|
||||
//void DeleteContextMenu(ContextMenu* contextMenu) {
|
||||
// // Free Menu
|
||||
// DeleteMenu(contextMenu->menu);
|
||||
//
|
||||
// // Delete any context menu data we may have stored
|
||||
// if( contextMenu->contextMenuData != NULL ) {
|
||||
// MEMFREE(contextMenu->contextMenuData);
|
||||
// }
|
||||
//
|
||||
// // Free JSON
|
||||
// if (contextMenu->processedJSON != NULL ) {
|
||||
// json_delete(contextMenu->processedJSON);
|
||||
// contextMenu->processedJSON = NULL;
|
||||
// }
|
||||
//
|
||||
// // Free context menu
|
||||
// free(contextMenu);
|
||||
//}
|
||||
//
|
||||
//int freeContextMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
// DeleteContextMenu(e->data);
|
||||
// return -1;
|
||||
//}
|
||||
//
|
||||
//void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData) {
|
||||
//
|
||||
// // If no context menu ID was given, abort
|
||||
// if( contextMenuID == NULL ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// ContextMenu* contextMenu = GetContextMenuByID(store, contextMenuID);
|
||||
//
|
||||
// // We don't need the ID now
|
||||
// MEMFREE(contextMenuID);
|
||||
//
|
||||
// if( contextMenu == NULL ) {
|
||||
// // Free context menu data
|
||||
// if( contextMenuData != NULL ) {
|
||||
// MEMFREE(contextMenuData);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // We need to store the context menu data. Free existing data if we have it
|
||||
// // and set to the new value.
|
||||
// FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
|
||||
//
|
||||
// // Grab the content view and show the menu
|
||||
// id contentView = msg(mainWindow, s("contentView"));
|
||||
//
|
||||
// // Get the triggering event
|
||||
// id menuEvent = msg(mainWindow, s("currentEvent"));
|
||||
//
|
||||
//// if( contextMenu->nsmenu == NULL ) {
|
||||
//// // GetMenu creates the NSMenu
|
||||
//// contextMenu->nsmenu = GetMenu(contextMenu->menu);
|
||||
//// }
|
||||
//
|
||||
// // Show popup
|
||||
// msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
|
||||
//
|
||||
//}
|
||||
//
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
//////
|
||||
////// Created by Lea Anthony on 6/1/21.
|
||||
//////
|
||||
////
|
||||
//// Created by Lea Anthony on 6/1/21.
|
||||
////
|
||||
//#ifndef CONTEXTMENU_DARWIN_H
|
||||
//#define CONTEXTMENU_DARWIN_H
|
||||
//
|
||||
#ifndef CONTEXTMENU_DARWIN_H
|
||||
#define CONTEXTMENU_DARWIN_H
|
||||
|
||||
#include "json.h"
|
||||
#include "menu_darwin.h"
|
||||
#include "contextmenustore_darwin.h"
|
||||
|
||||
typedef struct {
|
||||
const char* ID;
|
||||
id nsmenu;
|
||||
Menu* menu;
|
||||
|
||||
JsonNode* processedJSON;
|
||||
|
||||
// Context menu data is given by the frontend when clicking a context menu.
|
||||
// We send this to the backend when an item is selected
|
||||
const char* contextMenuData;
|
||||
} ContextMenu;
|
||||
|
||||
|
||||
ContextMenu* NewContextMenu(const char* contextMenuJSON);
|
||||
|
||||
ContextMenu* GetContextMenuByID( ContextMenuStore* store, const char *contextMenuID);
|
||||
void DeleteContextMenu(ContextMenu* contextMenu);
|
||||
int freeContextMenu(void *const context, struct hashmap_element_s *const e);
|
||||
|
||||
void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData);
|
||||
|
||||
#endif //CONTEXTMENU_DARWIN_H
|
||||
//#include "json.h"
|
||||
//#include "menu_darwin.h"
|
||||
//#include "contextmenustore_darwin.h"
|
||||
//
|
||||
//typedef struct {
|
||||
// const char* ID;
|
||||
// id nsmenu;
|
||||
// Menu* menu;
|
||||
//
|
||||
// JsonNode* processedJSON;
|
||||
//
|
||||
// // Context menu data is given by the frontend when clicking a context menu.
|
||||
// // We send this to the backend when an item is selected
|
||||
// const char* contextMenuData;
|
||||
//} ContextMenu;
|
||||
//
|
||||
//
|
||||
//ContextMenu* NewContextMenu(const char* contextMenuJSON);
|
||||
//
|
||||
//ContextMenu* GetContextMenuByID( ContextMenuStore* store, const char *contextMenuID);
|
||||
//void DeleteContextMenu(ContextMenu* contextMenu);
|
||||
//int freeContextMenu(void *const context, struct hashmap_element_s *const e);
|
||||
//
|
||||
//void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *contextMenuID, const char *contextMenuData);
|
||||
//
|
||||
//#endif //CONTEXTMENU_DARWIN_H
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "contextmenustore_darwin.h"
|
||||
|
||||
ContextMenuStore* NewContextMenuStore() {
|
||||
|
||||
ContextMenuStore* result = malloc(sizeof(ContextMenuStore));
|
||||
|
||||
// Allocate Context Menu Store
|
||||
if( 0 != hashmap_create((const unsigned)4, &result->contextMenuMap)) {
|
||||
ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON) {
|
||||
ContextMenu* newMenu = NewContextMenu(contextMenuJSON);
|
||||
|
||||
//TODO: check if there is already an entry for this menu
|
||||
hashmap_put(&store->contextMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
}
|
||||
|
||||
ContextMenu* GetContextMenuFromStore(ContextMenuStore* store, const char* menuID) {
|
||||
// Get the current menu
|
||||
return hashmap_get(&store->contextMenuMap, menuID, strlen(menuID));
|
||||
}
|
||||
|
||||
void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON) {
|
||||
ContextMenu* newContextMenu = NewContextMenu(menuJSON);
|
||||
|
||||
// Get the current menu
|
||||
ContextMenu *currentMenu = GetContextMenuFromStore(store, newContextMenu->ID);
|
||||
if ( currentMenu == NULL ) {
|
||||
ABORT("Attempted to update unknown context menu with ID '%s'.", newContextMenu->ID);
|
||||
}
|
||||
|
||||
hashmap_remove(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID));
|
||||
|
||||
// Save the status bar reference
|
||||
DeleteContextMenu(currentMenu);
|
||||
|
||||
hashmap_put(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID), newContextMenu);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void DeleteContextMenuStore(ContextMenuStore* store) {
|
||||
|
||||
// Guard against NULLs
|
||||
if( store == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete context menus
|
||||
if( hashmap_num_entries(&store->contextMenuMap) > 0 ) {
|
||||
if (0 != hashmap_iterate_pairs(&store->contextMenuMap, freeContextMenu, NULL)) {
|
||||
ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
|
||||
}
|
||||
}
|
||||
|
||||
// Free context menu hashmap
|
||||
hashmap_destroy(&store->contextMenuMap);
|
||||
|
||||
}
|
||||
//
|
||||
//#include "contextmenus_darwin.h"
|
||||
//#include "contextmenustore_darwin.h"
|
||||
//
|
||||
//ContextMenuStore* NewContextMenuStore() {
|
||||
//
|
||||
// ContextMenuStore* result = malloc(sizeof(ContextMenuStore));
|
||||
//
|
||||
// // Allocate Context Menu Store
|
||||
// if( 0 != hashmap_create((const unsigned)4, &result->contextMenuMap)) {
|
||||
// ABORT("[NewContextMenus] Not enough memory to allocate contextMenuStore!");
|
||||
// }
|
||||
//
|
||||
// return result;
|
||||
//}
|
||||
//
|
||||
//void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON) {
|
||||
// ContextMenu* newMenu = NewContextMenu(contextMenuJSON);
|
||||
//
|
||||
// //TODO: check if there is already an entry for this menu
|
||||
// hashmap_put(&store->contextMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
//}
|
||||
//
|
||||
//ContextMenu* GetContextMenuFromStore(ContextMenuStore* store, const char* menuID) {
|
||||
// // Get the current menu
|
||||
// return hashmap_get(&store->contextMenuMap, menuID, strlen(menuID));
|
||||
//}
|
||||
//
|
||||
//void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON) {
|
||||
// ContextMenu* newContextMenu = NewContextMenu(menuJSON);
|
||||
//
|
||||
// // Get the current menu
|
||||
// ContextMenu *currentMenu = GetContextMenuFromStore(store, newContextMenu->ID);
|
||||
// if ( currentMenu == NULL ) {
|
||||
// ABORT("Attempted to update unknown context menu with ID '%s'.", newContextMenu->ID);
|
||||
// }
|
||||
//
|
||||
// hashmap_remove(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID));
|
||||
//
|
||||
// // Save the status bar reference
|
||||
// DeleteContextMenu(currentMenu);
|
||||
//
|
||||
// hashmap_put(&store->contextMenuMap, newContextMenu->ID, strlen(newContextMenu->ID), newContextMenu);
|
||||
//
|
||||
//}
|
||||
//
|
||||
//
|
||||
//void DeleteContextMenuStore(ContextMenuStore* store) {
|
||||
//
|
||||
// // Guard against NULLs
|
||||
// if( store == NULL ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Delete context menus
|
||||
// if( hashmap_num_entries(&store->contextMenuMap) > 0 ) {
|
||||
// if (0 != hashmap_iterate_pairs(&store->contextMenuMap, freeContextMenu, NULL)) {
|
||||
// ABORT("[DeleteContextMenuStore] Failed to release contextMenuStore entries!");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Free context menu hashmap
|
||||
// hashmap_destroy(&store->contextMenuMap);
|
||||
//
|
||||
//}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
////
|
||||
//// Created by Lea Anthony on 7/1/21.
|
||||
////
|
||||
//
|
||||
// Created by Lea Anthony on 7/1/21.
|
||||
//#ifndef CONTEXTMENUSTORE_DARWIN_H
|
||||
//#define CONTEXTMENUSTORE_DARWIN_H
|
||||
//
|
||||
|
||||
#ifndef CONTEXTMENUSTORE_DARWIN_H
|
||||
#define CONTEXTMENUSTORE_DARWIN_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
int dummy;
|
||||
|
||||
// This is our context menu store which keeps track
|
||||
// of all instances of ContextMenus
|
||||
struct hashmap_s contextMenuMap;
|
||||
|
||||
} ContextMenuStore;
|
||||
|
||||
ContextMenuStore* NewContextMenuStore();
|
||||
|
||||
void DeleteContextMenuStore(ContextMenuStore* store);
|
||||
void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON);
|
||||
|
||||
void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON);
|
||||
|
||||
#endif //CONTEXTMENUSTORE_DARWIN_H
|
||||
//#include "common.h"
|
||||
//
|
||||
//typedef struct {
|
||||
//
|
||||
// int dummy;
|
||||
//
|
||||
// // This is our context menu store which keeps track
|
||||
// // of all instances of ContextMenus
|
||||
// struct hashmap_s contextMenuMap;
|
||||
//
|
||||
//} ContextMenuStore;
|
||||
//
|
||||
//ContextMenuStore* NewContextMenuStore();
|
||||
//
|
||||
//void DeleteContextMenuStore(ContextMenuStore* store);
|
||||
//void UpdateContextMenuInStore(ContextMenuStore* store, const char* menuJSON);
|
||||
//
|
||||
//void AddContextMenuToStore(ContextMenuStore* store, const char* contextMenuJSON);
|
||||
//
|
||||
//#endif //CONTEXTMENUSTORE_DARWIN_H
|
||||
|
||||
@@ -30,15 +30,15 @@ extern void Fullscreen(struct Application*);
|
||||
extern void UnFullscreen(struct Application*);
|
||||
extern void ToggleFullscreen(struct Application*);
|
||||
extern void DisableFrame(struct Application*);
|
||||
extern void OpenDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
|
||||
extern void SaveDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
|
||||
extern void MessageDialog(struct Application*, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton);
|
||||
extern void DarkModeEnabled(struct Application*, char *callbackID);
|
||||
extern void OpenDialog(struct Application*, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
|
||||
extern void SaveDialog(struct Application*, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
|
||||
extern void MessageDialog(struct Application*, const char *callbackID, const char *type, const char *title, const char *message, const char *icon, const char *button1, const char *button2, const char *button3, const char *button4, const char *defaultButton, const char *cancelButton);
|
||||
extern void DarkModeEnabled(struct Application*, const char *callbackID);
|
||||
extern void SetApplicationMenu(struct Application*, const char *);
|
||||
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
||||
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
||||
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
||||
extern void AddContextMenu(struct Application*, const char *contextMenuJSON);
|
||||
extern void UpdateContextMenu(struct Application*, const char *contextMenuJSON);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -12,9 +12,8 @@ package ffenestri
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"strconv"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
@@ -114,14 +113,6 @@ func (c *Client) WindowSize(width int, height int) {
|
||||
C.SetSize(c.app.app, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (c *Client) WindowSetMinSize(width int, height int) {
|
||||
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (c *Client) WindowSetMaxSize(width int, height int) {
|
||||
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
// WindowSetColour sets the window colour
|
||||
func (c *Client) WindowSetColour(colour int) {
|
||||
r, g, b, a := intToColour(colour)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
|
||||
#ifdef FFENESTRI_DARWIN
|
||||
|
||||
#include "ffenestri.h"
|
||||
#include "ffenestri_darwin.h"
|
||||
#include "menu_darwin.h"
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "traymenustore_darwin.h"
|
||||
#include "traymenu_darwin.h"
|
||||
#include "menu.h"
|
||||
//#include "contextmenus_darwin.h"
|
||||
//#include "traymenustore_darwin.h"
|
||||
//#include "traymenu_darwin.h"
|
||||
|
||||
// References to assets
|
||||
#include "assets.h"
|
||||
@@ -14,6 +13,10 @@ extern const unsigned char runtime;
|
||||
// Dialog icons
|
||||
extern const unsigned char *defaultDialogIcons[];
|
||||
#include "userdialogicons.h"
|
||||
#include "menu_darwin.h"
|
||||
|
||||
extern void messageFromWindowCallback(const char *);
|
||||
typedef void (*ffenestriCallback)(const char *);
|
||||
|
||||
// MAIN DEBUG FLAG
|
||||
int debug;
|
||||
@@ -49,9 +52,6 @@ void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
|
||||
printf("}\n");
|
||||
}
|
||||
|
||||
extern void messageFromWindowCallback(const char *);
|
||||
typedef void (*ffenestriCallback)(const char *);
|
||||
|
||||
void HideMouse() {
|
||||
msg(c("NSCursor"), s("hide"));
|
||||
}
|
||||
@@ -105,14 +105,16 @@ struct Application {
|
||||
int hideToolbarSeparator;
|
||||
int windowBackgroundIsTranslucent;
|
||||
|
||||
// Menu
|
||||
Menu *applicationMenu;
|
||||
// // Menu
|
||||
// Menu *applicationMenu;
|
||||
//
|
||||
// // Tray
|
||||
// TrayMenuStore* trayMenuStore;
|
||||
|
||||
// Tray
|
||||
TrayMenuStore* trayMenuStore;
|
||||
MenuManager* menuManager;
|
||||
|
||||
// Context Menus
|
||||
ContextMenuStore *contextMenuStore;
|
||||
// ContextMenuStore *contextMenuStore;
|
||||
|
||||
// Callback
|
||||
ffenestriCallback sendMessageToBackend;
|
||||
@@ -120,6 +122,9 @@ struct Application {
|
||||
// Bindings
|
||||
const char *bindings;
|
||||
|
||||
// Flags
|
||||
bool running;
|
||||
|
||||
};
|
||||
|
||||
// Debug works like sprintf but mutes if the global debug flag is true
|
||||
@@ -140,16 +145,6 @@ void Debug(struct Application *app, const char *message, ... ) {
|
||||
}
|
||||
}
|
||||
|
||||
void Error(struct Application *app, const char *message, ... ) {
|
||||
const char *temp = concat("LEFfenestri (C) | ", message);
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
|
||||
app->sendMessageToBackend(&logbuffer[0]);
|
||||
MEMFREE(temp);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void Fatal(struct Application *app, const char *message, ... ) {
|
||||
const char *temp = concat("LFFfenestri (C) | ", message);
|
||||
va_list args;
|
||||
@@ -160,12 +155,6 @@ void Fatal(struct Application *app, const char *message, ... ) {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// Requires NSString input EG lookupStringConstant(str("NSFontAttributeName"))
|
||||
void* lookupStringConstant(id constantName) {
|
||||
void ** dataPtr = CFBundleGetDataPointerForName(CFBundleGetBundleWithIdentifier((CFStringRef)str("com.apple.AppKit")), (CFStringRef) constantName);
|
||||
return (dataPtr ? *dataPtr : nil);
|
||||
}
|
||||
|
||||
bool isRetina(struct Application *app) {
|
||||
CGFloat scale = GET_BACKINGSCALEFACTOR(app->mainWindow);
|
||||
if( (int)scale == 1 ) {
|
||||
@@ -256,10 +245,7 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
struct Application *app = (struct Application *)objc_getAssociatedObject(
|
||||
self, "application");
|
||||
const char *name = (const char *)msg(msg(message, s("name")), s("UTF8String"));
|
||||
if( strcmp(name, "error") == 0 ) {
|
||||
printf("There was a Javascript error. Please open the devtools for more information.\n");
|
||||
Show(app);
|
||||
} else if( strcmp(name, "completed") == 0) {
|
||||
if( strcmp(name, "completed") == 0) {
|
||||
// Delete handler
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
|
||||
|
||||
@@ -268,6 +254,9 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
Show(app);
|
||||
}
|
||||
|
||||
// Setup initial trays
|
||||
ShowTrayMenus(app->menuManager);
|
||||
|
||||
// TODO: Check this actually does reduce flicker
|
||||
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
|
||||
|
||||
@@ -335,7 +324,8 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
const char* contextMenuData = STRCOPY(contextMenuDataNode->string_);
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData);
|
||||
//TODO: FIX UP
|
||||
// ShowContextMenu(app->contextMenuStore, app->mainWindow, contextMenuID, contextMenuData);
|
||||
);
|
||||
|
||||
json_delete(contextMenuMessageJSON);
|
||||
@@ -406,7 +396,7 @@ int releaseNSObject(void *const context, struct hashmap_element_s *const e) {
|
||||
}
|
||||
|
||||
void destroyContextMenus(struct Application *app) {
|
||||
DeleteContextMenuStore(app->contextMenuStore);
|
||||
// DeleteContextMenuStore(app->contextMenuStore);
|
||||
}
|
||||
|
||||
void freeDialogIconCache(struct Application *app) {
|
||||
@@ -439,19 +429,8 @@ void DestroyApplication(struct Application *app) {
|
||||
msg( c("NSEvent"), s("removeMonitor:"), app->mouseUpMonitor);
|
||||
}
|
||||
|
||||
// Delete the application menu if we have one
|
||||
if( app->applicationMenu != NULL ) {
|
||||
DeleteMenu(app->applicationMenu);
|
||||
}
|
||||
|
||||
// Delete the tray menu store
|
||||
DeleteTrayMenuStore(app->trayMenuStore);
|
||||
|
||||
// Delete the context menu store
|
||||
DeleteContextMenuStore(app->contextMenuStore);
|
||||
|
||||
// Destroy the context menus
|
||||
destroyContextMenus(app);
|
||||
DeleteMenuManager(app->menuManager);
|
||||
|
||||
// Free dialog icon cache
|
||||
freeDialogIconCache(app);
|
||||
@@ -463,7 +442,6 @@ void DestroyApplication(struct Application *app) {
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("error"));
|
||||
|
||||
// Close main window
|
||||
msg(app->mainWindow, s("close"));
|
||||
@@ -599,7 +577,7 @@ void SetPosition(struct Application *app, int x, int y) {
|
||||
);
|
||||
}
|
||||
|
||||
void processDialogButton(id alert, char *buttonTitle, char *cancelButton, char *defaultButton) {
|
||||
void processDialogButton(id alert, const char *buttonTitle, const char *cancelButton, const char *defaultButton) {
|
||||
// If this button is set
|
||||
if( STR_HAS_CHARS(buttonTitle) ) {
|
||||
id button = msg(alert, s("addButtonWithTitle:"), str(buttonTitle));
|
||||
@@ -612,11 +590,11 @@ void processDialogButton(id alert, char *buttonTitle, char *cancelButton, char *
|
||||
}
|
||||
}
|
||||
|
||||
extern void MessageDialog(struct Application *app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {
|
||||
extern void MessageDialog(struct Application *app, const char *callbackID, const char *type, const char *title, const char *message, const char *icon, const char *button1, const char *button2, const char *button3, const char *button4, const char *defaultButton, const char *cancelButton) {
|
||||
ON_MAIN_THREAD(
|
||||
id alert = ALLOC_INIT("NSAlert");
|
||||
char *dialogType = type;
|
||||
char *dialogIcon = type;
|
||||
const char *dialogType = type;
|
||||
const char *dialogIcon = type;
|
||||
|
||||
// Default to info type
|
||||
if( dialogType == NULL ) {
|
||||
@@ -694,7 +672,7 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
|
||||
}
|
||||
|
||||
// Run modal
|
||||
char *buttonPressed;
|
||||
const char *buttonPressed;
|
||||
int response = (int)msg(alert, s("runModal"));
|
||||
if( response == NSAlertFirstButtonReturn ) {
|
||||
buttonPressed = button1;
|
||||
@@ -725,7 +703,7 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
|
||||
}
|
||||
|
||||
// OpenDialog opens a dialog to select files/directories
|
||||
void OpenDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
|
||||
void OpenDialog(struct Application *app, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
|
||||
Debug(app, "OpenDialog Called with callback id: %s", callbackID);
|
||||
|
||||
// Create an open panel
|
||||
@@ -813,7 +791,7 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
|
||||
}
|
||||
|
||||
// SaveDialog opens a dialog to select files/directories
|
||||
void SaveDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
|
||||
void SaveDialog(struct Application *app, const char *callbackID, const char *title, const char *filters, const char *defaultFilename, const char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
|
||||
Debug(app, "SaveDialog Called with callback id: %s", callbackID);
|
||||
|
||||
// Create an open panel
|
||||
@@ -899,20 +877,6 @@ void setMinMaxSize(struct Application *app)
|
||||
{
|
||||
msg(app->mainWindow, s("setMinSize:"), CGSizeMake(app->minWidth, app->minHeight));
|
||||
}
|
||||
|
||||
// Calculate if window needs resizing
|
||||
int newWidth = app->width;
|
||||
int newHeight = app->height;
|
||||
|
||||
if (app->maxWidth > 0 && app->width > app->maxWidth) newWidth = app->maxWidth;
|
||||
if (app->minWidth > 0 && app->width < app->minWidth) newWidth = app->minWidth;
|
||||
if (app->maxHeight > 0 && app->height > app->maxHeight ) newHeight = app->maxHeight;
|
||||
if (app->minHeight > 0 && app->height < app->minHeight ) newHeight = app->minHeight;
|
||||
|
||||
// If we have any change, resize window
|
||||
if ( newWidth != app->width || newHeight != app->height ) {
|
||||
SetSize(app, newWidth, newHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
|
||||
@@ -942,7 +906,7 @@ void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
|
||||
}
|
||||
|
||||
|
||||
void SetDebug(void *applicationPointer, int flag) {
|
||||
void SetDebug(struct Application *applicationPointer, int flag) {
|
||||
debug = flag;
|
||||
}
|
||||
|
||||
@@ -950,28 +914,31 @@ void SetDebug(void *applicationPointer, int flag) {
|
||||
|
||||
// AddContextMenu sets the context menu map for this application
|
||||
void AddContextMenu(struct Application *app, const char *contextMenuJSON) {
|
||||
AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
|
||||
// AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
|
||||
}
|
||||
|
||||
void UpdateContextMenu(struct Application *app, const char* contextMenuJSON) {
|
||||
UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON);
|
||||
// UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON);
|
||||
}
|
||||
|
||||
void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
|
||||
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
|
||||
AddTrayMenuToManager(app->menuManager, trayMenuJSON);
|
||||
}
|
||||
|
||||
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
||||
ON_MAIN_THREAD(
|
||||
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
|
||||
);
|
||||
TrayMenu *menu = AddTrayMenuToManager(app->menuManager, trayMenuJSON);
|
||||
if( app->running ) {
|
||||
ON_MAIN_THREAD(
|
||||
ShowTrayMenu(menu)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
||||
ON_MAIN_THREAD(
|
||||
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
|
||||
);
|
||||
}
|
||||
//void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
||||
// ON_MAIN_THREAD(
|
||||
// UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
|
||||
// );
|
||||
//}
|
||||
|
||||
|
||||
void SetBindings(struct Application *app, const char *bindings) {
|
||||
@@ -1060,7 +1027,7 @@ void createDelegate(struct Application *app) {
|
||||
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||
|
||||
// All Menu Items use a common callback
|
||||
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
|
||||
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)PlatformMenuItemCallback, "v@:@");
|
||||
|
||||
// Script handler
|
||||
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
||||
@@ -1117,402 +1084,69 @@ const char* getInitialState(struct Application *app) {
|
||||
return result;
|
||||
}
|
||||
|
||||
void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) {
|
||||
const char *roleName = item->string_;
|
||||
|
||||
if ( STREQ(roleName, "appMenu") ) {
|
||||
createDefaultAppMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "editMenu")) {
|
||||
createDefaultEditMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hide")) {
|
||||
addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hideothers")) {
|
||||
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "unhide")) {
|
||||
addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "front")) {
|
||||
addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "undo")) {
|
||||
addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "redo")) {
|
||||
addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "cut")) {
|
||||
addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "copy")) {
|
||||
addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "paste")) {
|
||||
addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "delete")) {
|
||||
addMenuItem(parentMenu, "Delete", "delete:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if( STREQ(roleName, "pasteandmatchstyle")) {
|
||||
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
|
||||
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
||||
}
|
||||
if ( STREQ(roleName, "selectall")) {
|
||||
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "minimize")) {
|
||||
addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "zoom")) {
|
||||
addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "quit")) {
|
||||
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "togglefullscreen")) {
|
||||
addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
id parseTextMenuItem(struct Application *app, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char *menuCallback) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s(menuCallback), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
|
||||
// Process modifiers
|
||||
if( modifiers != NULL ) {
|
||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
}
|
||||
msg(parentMenu, s("addItem:"), item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
id parseCheckboxMenuItem(struct Application *app, id parentmenu, const char
|
||||
*title, const char *menuid, bool disabled, bool checked, const char *key,
|
||||
struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Store the item in the menu item map
|
||||
hashmap_put(menuItemMap, (char*)menuid, strlen(menuid), item);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s(checkboxCallbackFunction), str(key));
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id parseRadioMenuItem(struct Application *app, id parentmenu, const char *title,
|
||||
const char *menuid, bool disabled, bool checked, const char *acceleratorkey,
|
||||
struct hashmap_s *menuItemMap, const char *radioCallbackFunction) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Store the item in the menu item map
|
||||
hashmap_put(menuItemMap, (char*)menuid, strlen(menuid), item);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuid);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s(radioCallbackFunction), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
void parseMenuItem(struct Application *app, id parentMenu, JsonNode *item,
|
||||
struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char
|
||||
*radioCallbackFunction, const char *menuCallbackFunction) {
|
||||
|
||||
// Check if this item is hidden and if so, exit early!
|
||||
bool hidden = false;
|
||||
getJSONBool(item, "Hidden", &hidden);
|
||||
if( hidden ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the role
|
||||
JsonNode *role = json_find_member(item, "Role");
|
||||
if( role != NULL ) {
|
||||
parseMenuRole(app, parentMenu, role);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a submenu
|
||||
JsonNode *submenu = json_find_member(item, "SubMenu");
|
||||
if( submenu != NULL ) {
|
||||
// Get the label
|
||||
JsonNode *menuNameNode = json_find_member(item, "Label");
|
||||
const char *name = "";
|
||||
if ( menuNameNode != NULL) {
|
||||
name = menuNameNode->string_;
|
||||
}
|
||||
|
||||
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||
id thisMenu = createMenu(str(name));
|
||||
|
||||
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||
msg(parentMenu, s("addItem:"), thisMenuItem);
|
||||
|
||||
// Loop over submenu items
|
||||
JsonNode *item;
|
||||
json_foreach(item, submenu) {
|
||||
// Get item label
|
||||
parseMenuItem(app, thisMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a user menu. Get the common data
|
||||
// Get the label
|
||||
const char *label = getJSONString(item, "Label");
|
||||
if ( label == NULL) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
const char *menuid = getJSONString(item, "ID");
|
||||
if ( menuid == NULL) {
|
||||
menuid = "";
|
||||
}
|
||||
|
||||
bool disabled = false;
|
||||
getJSONBool(item, "Disabled", &disabled);
|
||||
|
||||
// Get the Accelerator
|
||||
JsonNode *accelerator = json_find_member(item, "Accelerator");
|
||||
const char *acceleratorkey = NULL;
|
||||
const char **modifiers = NULL;
|
||||
|
||||
// If we have an accelerator
|
||||
if( accelerator != NULL ) {
|
||||
// Get the key
|
||||
acceleratorkey = getJSONString(accelerator, "Key");
|
||||
// Check if there are modifiers
|
||||
JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
|
||||
if ( modifiersList != NULL ) {
|
||||
// Allocate an array of strings
|
||||
int noOfModifiers = json_array_length(modifiersList);
|
||||
|
||||
// Do we have any?
|
||||
if (noOfModifiers > 0) {
|
||||
modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
|
||||
JsonNode *modifier;
|
||||
int count = 0;
|
||||
// Iterate the modifiers and save a reference to them in our new array
|
||||
json_foreach(modifier, modifiersList) {
|
||||
// Get modifier name
|
||||
modifiers[count] = modifier->string_;
|
||||
count++;
|
||||
}
|
||||
// Null terminate the modifier list
|
||||
modifiers[count] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get the Type
|
||||
JsonNode *type = json_find_member(item, "Type");
|
||||
if( type != NULL ) {
|
||||
|
||||
if( STREQ(type->string_, "Text")) {
|
||||
parseTextMenuItem(app, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, menuCallbackFunction);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Separator")) {
|
||||
addSeparator(parentMenu);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Checkbox")) {
|
||||
// Get checked state
|
||||
bool checked = false;
|
||||
getJSONBool(item, "Checked", &checked);
|
||||
|
||||
parseCheckboxMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, checkboxCallbackFunction);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Radio")) {
|
||||
// Get checked state
|
||||
bool checked = false;
|
||||
getJSONBool(item, "Checked", &checked);
|
||||
|
||||
parseRadioMenuItem(app, parentMenu, label, menuid, disabled, checked, "", menuItemMap, radioCallbackFunction);
|
||||
}
|
||||
|
||||
if ( modifiers != NULL ) {
|
||||
free(modifiers);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void parseMenu(struct Application *app, id parentMenu, JsonNode *menu, struct hashmap_s *menuItemMap, const char *checkboxCallbackFunction, const char *radioCallbackFunction, const char *menuCallbackFunction) {
|
||||
JsonNode *items = json_find_member(menu, "Items");
|
||||
if( items == NULL ) {
|
||||
// Parse error!
|
||||
Fatal(app, "Unable to find Items in Menu");
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate items
|
||||
JsonNode *item;
|
||||
json_foreach(item, items) {
|
||||
// Get item label
|
||||
parseMenuItem(app, parentMenu, item, menuItemMap, checkboxCallbackFunction, radioCallbackFunction, menuCallbackFunction);
|
||||
}
|
||||
}
|
||||
|
||||
void dumpMemberList(const char *name, id *memberList) {
|
||||
void *member = memberList[0];
|
||||
int count = 0;
|
||||
printf("%s = %p -> [ ", name, memberList);
|
||||
while( member != NULL ) {
|
||||
printf("%p ", member);
|
||||
count = count + 1;
|
||||
member = memberList[count];
|
||||
}
|
||||
printf("]\n");
|
||||
}
|
||||
|
||||
void processRadioGroup(JsonNode *radioGroup, struct hashmap_s *menuItemMap,
|
||||
struct hashmap_s *radioGroupMap) {
|
||||
|
||||
int groupLength;
|
||||
getJSONInt(radioGroup, "Length", &groupLength);
|
||||
JsonNode *members = json_find_member(radioGroup, "Members");
|
||||
JsonNode *member;
|
||||
|
||||
// Allocate array
|
||||
size_t arrayLength = sizeof(id)*(groupLength+1);
|
||||
id memberList[arrayLength];
|
||||
|
||||
// Build the radio group items
|
||||
int count=0;
|
||||
json_foreach(member, members) {
|
||||
// Get menu by id
|
||||
id menuItem = (id)hashmap_get(menuItemMap, (char*)member->string_, strlen(member->string_));
|
||||
// Save Member
|
||||
memberList[count] = menuItem;
|
||||
count = count + 1;
|
||||
}
|
||||
// Null terminate array
|
||||
memberList[groupLength] = 0;
|
||||
|
||||
// dumpMemberList("memberList", memberList);
|
||||
|
||||
// Store the members
|
||||
json_foreach(member, members) {
|
||||
// Copy the memberList
|
||||
char *newMemberList = (char *)malloc(arrayLength);
|
||||
memcpy(newMemberList, memberList, arrayLength);
|
||||
// add group to each member of group
|
||||
hashmap_put(radioGroupMap, member->string_, strlen(member->string_), newMemberList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// updateMenu replaces the current menu with the given one
|
||||
void updateMenu(struct Application *app, const char *menuAsJSON) {
|
||||
Debug(app, "Menu is now: %s", menuAsJSON);
|
||||
ON_MAIN_THREAD (
|
||||
DeleteMenu(app->applicationMenu);
|
||||
Menu* newMenu = NewApplicationMenu(menuAsJSON);
|
||||
id menu = GetMenu(newMenu);
|
||||
app->applicationMenu = newMenu;
|
||||
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
|
||||
);
|
||||
}
|
||||
//void dumpMemberList(const char *name, id *memberList) {
|
||||
// void *member = memberList[0];
|
||||
// int count = 0;
|
||||
// printf("%s = %p -> [ ", name, memberList);
|
||||
// while( member != NULL ) {
|
||||
// printf("%p ", member);
|
||||
// count = count + 1;
|
||||
// member = memberList[count];
|
||||
// }
|
||||
// printf("]\n");
|
||||
//}
|
||||
|
||||
// SetApplicationMenu sets the initial menu for the application
|
||||
void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
|
||||
if ( app->applicationMenu == NULL ) {
|
||||
app->applicationMenu = NewApplicationMenu(menuAsJSON);
|
||||
return;
|
||||
}
|
||||
//void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
|
||||
// if ( app->applicationMenu == NULL ) {
|
||||
// app->applicationMenu = NewApplicationMenu(menuAsJSON, (struct TrayMenuStore *) app->trayMenuStore);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Update menu
|
||||
// updateMenu(app, menuAsJSON);
|
||||
//}
|
||||
|
||||
// Update menu
|
||||
updateMenu(app, menuAsJSON);
|
||||
}
|
||||
|
||||
void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) {
|
||||
|
||||
unsigned int count = 0;
|
||||
while( 1 ) {
|
||||
const unsigned char *name = dialogIcons[count++];
|
||||
if( name == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
const unsigned char *lengthAsString = dialogIcons[count++];
|
||||
if( name == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
const unsigned char *data = dialogIcons[count++];
|
||||
if( data == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
int length = atoi((const char *)lengthAsString);
|
||||
|
||||
// Create the icon and add to the hashmap
|
||||
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
|
||||
id dialogImage = ALLOC("NSImage");
|
||||
msg(dialogImage, s("initWithData:"), imageData);
|
||||
hashmap_put(hashmap, (const char *)name, strlen((const char *)name), dialogImage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void processUserDialogIcons(struct Application *app) {
|
||||
|
||||
// Allocate the Dialog icon hashmap
|
||||
if( 0 != hashmap_create((const unsigned)4, &dialogIconCache)) {
|
||||
// Couldn't allocate map
|
||||
Fatal(app, "Not enough memory to allocate dialogIconCache!");
|
||||
return;
|
||||
}
|
||||
|
||||
processDialogIcons(&dialogIconCache, defaultDialogIcons);
|
||||
processDialogIcons(&dialogIconCache, userDialogIcons);
|
||||
|
||||
}
|
||||
//void processDialogIcons(struct hashmap_s *hashmap, const unsigned char *dialogIcons[]) {
|
||||
//
|
||||
// unsigned int count = 0;
|
||||
// while( 1 ) {
|
||||
// const unsigned char *name = dialogIcons[count++];
|
||||
// if( name == 0x00 ) {
|
||||
// break;
|
||||
// }
|
||||
// const unsigned char *lengthAsString = dialogIcons[count++];
|
||||
// if( name == 0x00 ) {
|
||||
// break;
|
||||
// }
|
||||
// const unsigned char *data = dialogIcons[count++];
|
||||
// if( data == 0x00 ) {
|
||||
// break;
|
||||
// }
|
||||
// int length = atoi((const char *)lengthAsString);
|
||||
//
|
||||
// // Create the icon and add to the hashmap
|
||||
// id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
|
||||
// id dialogImage = ALLOC("NSImage");
|
||||
// msg(dialogImage, s("initWithData:"), imageData);
|
||||
// hashmap_put(hashmap, (const char *)name, strlen((const char *)name), dialogImage);
|
||||
// }
|
||||
//
|
||||
//}
|
||||
//
|
||||
//void processUserDialogIcons(struct Application *app) {
|
||||
//
|
||||
// // Allocate the Dialog icon hashmap
|
||||
// if( 0 != hashmap_create((const unsigned)4, &dialogIconCache)) {
|
||||
// // Couldn't allocate map
|
||||
// Fatal(app, "Not enough memory to allocate dialogIconCache!");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// processDialogIcons(&dialogIconCache, defaultDialogIcons);
|
||||
// processDialogIcons(&dialogIconCache, userDialogIcons);
|
||||
//
|
||||
//}
|
||||
|
||||
|
||||
void Run(struct Application *app, int argc, char **argv) {
|
||||
@@ -1562,7 +1196,6 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
id manager = msg(config, s("userContentController"));
|
||||
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("external"));
|
||||
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("completed"));
|
||||
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("error"));
|
||||
app->manager = manager;
|
||||
|
||||
id wkwebview = msg(c("WKWebView"), s("alloc"));
|
||||
@@ -1689,17 +1322,15 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
msg(wkwebview, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("drawsBackground"));
|
||||
}
|
||||
|
||||
// If we have an application menu, process it
|
||||
if( app->applicationMenu != NULL ) {
|
||||
id menu = GetMenu(app->applicationMenu);
|
||||
msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), menu);
|
||||
}
|
||||
|
||||
// Setup initial trays
|
||||
ShowTrayMenusInStore(app->trayMenuStore);
|
||||
// // If we have an application menu, process it
|
||||
// if( app->applicationMenu != NULL ) {
|
||||
// msg(msg(c("NSApplication"), s("sharedApplication")), s("setMainMenu:"), app->applicationMenu->menu);
|
||||
// }
|
||||
|
||||
// Process dialog icons
|
||||
processUserDialogIcons(app);
|
||||
// processUserDialogIcons(app);
|
||||
|
||||
app->running = true;
|
||||
|
||||
// Finally call run
|
||||
Debug(app, "Run called");
|
||||
@@ -1709,7 +1340,7 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
}
|
||||
|
||||
|
||||
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
|
||||
struct Application* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
|
||||
|
||||
// Load the tray icons
|
||||
LoadTrayIcons();
|
||||
@@ -1750,13 +1381,10 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
result->delegate = NULL;
|
||||
|
||||
// Menu
|
||||
result->applicationMenu = NULL;
|
||||
|
||||
// Tray
|
||||
result->trayMenuStore = NewTrayMenuStore();
|
||||
result->menuManager = NewMenuManager();
|
||||
|
||||
// Context Menus
|
||||
result->contextMenuStore = NewContextMenuStore();
|
||||
// result->contextMenuStore = NewContextMenuStore();
|
||||
|
||||
// Window Appearance
|
||||
result->titlebarAppearsTransparent = 0;
|
||||
@@ -1764,8 +1392,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
|
||||
result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback;
|
||||
|
||||
result->running = false;
|
||||
|
||||
return (void*) result;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,7 +2,7 @@ package ffenestri
|
||||
|
||||
/*
|
||||
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
|
||||
#cgo darwin LDFLAGS: -framework WebKit -framework CoreFoundation -lobjc
|
||||
#cgo darwin LDFLAGS: -framework WebKit -lobjc
|
||||
|
||||
#include "ffenestri.h"
|
||||
#include "ffenestri_darwin.h"
|
||||
@@ -58,15 +58,15 @@ func (a *Application) processPlatformSettings() error {
|
||||
C.WindowBackgroundIsTranslucent(a.app)
|
||||
}
|
||||
|
||||
// Process menu
|
||||
//applicationMenu := options.GetApplicationMenu(a.config)
|
||||
applicationMenu := a.menuManager.GetApplicationMenuJSON()
|
||||
if applicationMenu != "" {
|
||||
C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
|
||||
}
|
||||
//// Process menu
|
||||
////applicationMenu := options.GetApplicationMenu(a.config)
|
||||
//applicationMenu := a.menuManager.GetApplicationMenuJSON()
|
||||
//if applicationMenu != "" {
|
||||
// C.SetApplicationMenu(a.app, a.string2CString(applicationMenu))
|
||||
//}
|
||||
|
||||
// Process tray
|
||||
trays, err := a.menuManager.GetTrayMenus()
|
||||
trays, err := a.menuManager.GetTrayMenusAsJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,16 +76,16 @@ func (a *Application) processPlatformSettings() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Process context menus
|
||||
contextMenus, err := a.menuManager.GetContextMenus()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if contextMenus != nil {
|
||||
for _, contextMenu := range contextMenus {
|
||||
C.AddContextMenu(a.app, a.string2CString(contextMenu))
|
||||
}
|
||||
}
|
||||
//// Process context menus
|
||||
//contextMenus, err := a.menuManager.GetContextMenus()
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//if contextMenus != nil {
|
||||
// for _, contextMenu := range contextMenus {
|
||||
// C.AddContextMenu(a.app, a.string2CString(contextMenu))
|
||||
// }
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
|
||||
#include <objc/objc-runtime.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include "json.h"
|
||||
#include "hashmap.h"
|
||||
#include "stdlib.h"
|
||||
@@ -21,6 +20,8 @@
|
||||
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
|
||||
#define cstr(input) (const char *)msg(input, s("UTF8String"))
|
||||
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
|
||||
#define RELEASE(input) if( input != NULL ) { msg(input, s("release")); }
|
||||
|
||||
#define ALLOC(classname) msg(c(classname), s("alloc"))
|
||||
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
|
||||
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
|
||||
@@ -108,8 +109,5 @@ void WebviewIsTransparent(struct Application* app);
|
||||
void WindowBackgroundIsTranslucent(struct Application* app);
|
||||
void SetTray(struct Application* app, const char *, const char *, const char *);
|
||||
//void SetContextMenus(struct Application* app, const char *);
|
||||
void AddTrayMenu(struct Application* app, const char *);
|
||||
|
||||
void* lookupStringConstant(id constantName);
|
||||
|
||||
#endif
|
||||
276
v2/internal/ffenestri/menu.c
Normal file
276
v2/internal/ffenestri/menu.c
Normal file
@@ -0,0 +1,276 @@
|
||||
//
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#include "menu.h"
|
||||
|
||||
Menu* NewMenu(struct JsonNode* menuData, struct JsonNode* radioData, MenuManager* manager) {
|
||||
|
||||
if( menuData == NULL ) return NULL;
|
||||
if( manager == NULL ) return NULL;
|
||||
|
||||
Menu *result = NEW(Menu);
|
||||
|
||||
// No label by default
|
||||
result->label = STRCOPY(getJSONStringDefault(menuData, "l", ""));
|
||||
|
||||
// Setup platform specific menu data
|
||||
SetupMenuPlatformData(result);
|
||||
|
||||
// Init menu item list
|
||||
vec_init(&result->menuItems);
|
||||
|
||||
// Get the menu items
|
||||
JsonNode* items = getJSONObject(menuData, "i");
|
||||
if( items != NULL ) {
|
||||
|
||||
// Process the menu data
|
||||
JsonNode *item;
|
||||
json_foreach(item, items) {
|
||||
const char *ID = mustJSONString(item, "I");
|
||||
MenuItem *menuItem = HASHMAP_GET(manager->menuItems, ID);
|
||||
if (menuItem == NULL) {
|
||||
// Process each menu item
|
||||
menuItem = processMenuItem(result, item, manager);
|
||||
|
||||
// Filter out separators
|
||||
if (menuItem->ID != NULL) {
|
||||
HASHMAP_PUT(manager->menuItems, menuItem->ID, menuItem);
|
||||
}
|
||||
}
|
||||
AddMenuItemToMenu(result, menuItem, manager);
|
||||
}
|
||||
|
||||
if (radioData != NULL) {
|
||||
// Iterate radio groups
|
||||
JsonNode *radioGroup;
|
||||
json_foreach(radioGroup, radioData) {
|
||||
// Get item label
|
||||
processRadioGroup(result, radioGroup, manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MenuItem* processMenuItem(Menu *menu, JsonNode *item, MenuManager *manager) {
|
||||
|
||||
|
||||
// Get the role
|
||||
const char *role = getJSONString(item, "r");
|
||||
if( role != NULL ) {
|
||||
// Roles override everything else
|
||||
// return NewMenuItemForRole(role, menu, item, manager);
|
||||
}
|
||||
|
||||
Menu* submenu = NULL;
|
||||
|
||||
// Check if this is a submenu
|
||||
// JsonNode *submenuData = json_find_member(item, "S");
|
||||
// if( submenuData != NULL ) {
|
||||
// submenu = NewMenu(submenuData)
|
||||
// // Get the label
|
||||
// JsonNode *menuNameNode = json_find_member(item, "l");
|
||||
// const char *name = "";
|
||||
// if ( menuNameNode != NULL) {
|
||||
// name = menuNameNode->string_;
|
||||
// }
|
||||
//
|
||||
// id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||
// id thisMenu = createMenu(str(name));
|
||||
//
|
||||
// msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||
// msg(parentMenu, s("addItem:"), thisMenuItem);
|
||||
//
|
||||
// JsonNode *submenuItems = json_find_member(submenu, "i");
|
||||
// // If we have no items, just return
|
||||
// if ( submenuItems == NULL ) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Loop over submenu items
|
||||
// JsonNode *item;
|
||||
// json_foreach(item, submenuItems) {
|
||||
// // Get item label
|
||||
// processMenuItem(menu, thisMenu, item);
|
||||
// }
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
|
||||
|
||||
// Get the ID
|
||||
const char *menuItemID = mustJSONString(item, "I");
|
||||
|
||||
// Get the label(s)
|
||||
const char* label = getJSONStringDefault(item, "l", "");
|
||||
const char* alternateLabel = getJSONString(item, "L");
|
||||
|
||||
bool checked = getJSONBool(item, "c");
|
||||
bool hidden = getJSONBool(item, "h");
|
||||
bool disabled = getJSONBool(item, "d");
|
||||
const char* RGBA = getJSONString(item, "R");
|
||||
const char* font = getJSONString(item, "F");
|
||||
const char* image = getJSONString(item, "i");
|
||||
|
||||
int fontSize = 0;
|
||||
getJSONInt(item, "F", &fontSize);
|
||||
|
||||
// Get the Accelerator
|
||||
JsonNode *accelerator = json_find_member(item, "a");
|
||||
const char *acceleratorKey = NULL;
|
||||
const char **modifiers = NULL;
|
||||
|
||||
// If we have an accelerator
|
||||
if( accelerator != NULL ) {
|
||||
// Get the key
|
||||
acceleratorKey = getJSONString(accelerator, "Key");
|
||||
// Check if there are modifiers
|
||||
JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
|
||||
if ( modifiersList != NULL ) {
|
||||
// Allocate an array of strings
|
||||
int noOfModifiers = json_array_length(modifiersList);
|
||||
|
||||
// Do we have any?
|
||||
if (noOfModifiers > 0) {
|
||||
modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
|
||||
JsonNode *modifier;
|
||||
int count = 0;
|
||||
// Iterate the modifiers and save a reference to them in our new array
|
||||
json_foreach(modifier, modifiersList) {
|
||||
// Get modifier name
|
||||
modifiers[count] = STRCOPY(modifier->string_);
|
||||
count++;
|
||||
}
|
||||
// Null terminate the modifier list
|
||||
modifiers[count] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// has callback?
|
||||
bool hasCallback = getJSONBool(item, "C");
|
||||
|
||||
// Get the Type
|
||||
const char *type = mustJSONString(item, "t");
|
||||
|
||||
MenuItem* menuItem;
|
||||
enum MenuItemType menuItemType;
|
||||
if( STREQ(type, "t")) {
|
||||
menuItemType = Text;
|
||||
}
|
||||
else if ( STREQ(type, "s")) {
|
||||
menuItemType = Separator;
|
||||
}
|
||||
else if ( STREQ(type, "c")) {
|
||||
menuItemType = Checkbox;
|
||||
}
|
||||
else if ( STREQ(type, "r")) {
|
||||
menuItemType = Radio;
|
||||
} else {
|
||||
menuItemType = -1;
|
||||
ABORT("Unknown Menu Item type '%s'!", type);
|
||||
}
|
||||
menuItem = NewMenuItem(menuItemType, menuItemID, label, alternateLabel, disabled, hidden, checked, RGBA, font, fontSize, image, acceleratorKey, modifiers, hasCallback, submenu);
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
void DeleteMenu(Menu* menu) {
|
||||
|
||||
// NULL guard
|
||||
if( menu == NULL ) return;
|
||||
|
||||
// Delete the platform specific menu data
|
||||
DeleteMenuPlatformData(menu);
|
||||
|
||||
// Clean up other data
|
||||
MEMFREE(menu->label);
|
||||
|
||||
// Clear the menu items vector
|
||||
vec_deinit(&menu->menuItems);
|
||||
}
|
||||
|
||||
const char* MenuAsJSON(Menu* menu) {
|
||||
|
||||
if( menu == NULL ) return NULL;
|
||||
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
JSON_ADD_STRING(jsonObject, "Label", menu->label);
|
||||
return json_encode(jsonObject);
|
||||
}
|
||||
|
||||
JsonNode* MenuAsJSONObject(Menu* menu) {
|
||||
|
||||
if( menu == NULL ) return NULL;
|
||||
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
JSON_ADD_STRING(jsonObject, "Label", menu->label);
|
||||
if( vec_size(&menu->menuItems) > 0 ) {
|
||||
JsonNode* items = json_mkarray();
|
||||
int i; MenuItem *menuItem;
|
||||
vec_foreach(&menu->menuItems, menuItem, i) {
|
||||
JSON_ADD_ELEMENT(items, MenuItemAsJSONObject(menuItem));
|
||||
}
|
||||
JSON_ADD_OBJECT(jsonObject, "Items", items);
|
||||
}
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
|
||||
void processRadioGroup(Menu *menu, JsonNode *radioGroup, MenuManager* manager) {
|
||||
|
||||
JsonNode *members = json_find_member(radioGroup, "Members");
|
||||
JsonNode *member;
|
||||
|
||||
int groupLength = json_array_length(radioGroup);
|
||||
// Allocate array
|
||||
size_t arrayLength = sizeof(id)*(groupLength+1);
|
||||
MenuItem* memberList[arrayLength];
|
||||
|
||||
// Build the radio group items
|
||||
int count=0;
|
||||
json_foreach(member, members) {
|
||||
// Get menu by id
|
||||
MenuItem* menuItem = HASHMAP_GET(manager->menuItems, (char*)member->string_);
|
||||
// Save Member
|
||||
memberList[count] = menuItem;
|
||||
count = count + 1;
|
||||
}
|
||||
// Null terminate array
|
||||
memberList[groupLength] = 0;
|
||||
|
||||
// Store the members
|
||||
json_foreach(member, members) {
|
||||
// Copy the memberList
|
||||
char *newMemberList = (char *)malloc(arrayLength);
|
||||
memcpy(newMemberList, memberList, arrayLength);
|
||||
// add group to each member of group
|
||||
HASHMAP_PUT(menu->radioGroups, member->string_, newMemberList);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AddMenuItemToMenu(Menu* menu, MenuItem* menuItem, MenuManager* manager) {
|
||||
vec_push(&menu->menuItems, menuItem);
|
||||
PlatformAddMenuItemToMenu(menu, menuItem, manager);
|
||||
}
|
||||
|
||||
// Creates a JSON message for the given menuItemID and data
|
||||
const char* CreateMenuClickedMessage(const char *menuItemID, const char *data) {
|
||||
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
if (menuItemID == NULL ) {
|
||||
ABORT("Item ID NULL for menu!!\n");
|
||||
}
|
||||
json_append_member(jsonObject, "i", json_mkstring(menuItemID));
|
||||
if (data != NULL) {
|
||||
json_append_member(jsonObject, "data", json_mkstring(data));
|
||||
}
|
||||
const char *payload = json_encode(jsonObject);
|
||||
json_delete(jsonObject);
|
||||
const char *result = concat("MC", payload);
|
||||
MEMFREE(payload);
|
||||
return result;
|
||||
}
|
||||
178
v2/internal/ffenestri/menu.h
Normal file
178
v2/internal/ffenestri/menu.h
Normal file
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#ifndef MENU_H
|
||||
#define MENU_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2, Separator = 3};
|
||||
static const char *MenuItemTypeAsString[] = {
|
||||
"Text", "Checkbox", "Radio", "Separator",
|
||||
};
|
||||
|
||||
enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
|
||||
static const char *MenuTypeAsString[] = {
|
||||
"ApplicationMenu", "ContextMenu", "TrayMenu",
|
||||
};
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
||||
// Menu label
|
||||
const char *label;
|
||||
|
||||
// A list of menuItem IDs that make up this menu
|
||||
vec_void_t menuItems;
|
||||
|
||||
// The platform specific menu data
|
||||
void *platformData;
|
||||
|
||||
} Menu;
|
||||
|
||||
typedef struct {
|
||||
|
||||
// ID of the tray
|
||||
const char *ID;
|
||||
|
||||
// The tray label
|
||||
const char *Label;
|
||||
|
||||
// The icon name
|
||||
const char *Icon;
|
||||
|
||||
// The menu
|
||||
Menu* Menu;
|
||||
|
||||
// Platform specific data
|
||||
void* platformData;
|
||||
|
||||
} TrayMenu;
|
||||
|
||||
typedef struct {
|
||||
|
||||
Menu* menu;
|
||||
|
||||
} ApplicationMenu;
|
||||
|
||||
typedef struct {
|
||||
|
||||
const char* ID;
|
||||
|
||||
Menu* menu;
|
||||
|
||||
} ContextMenu;
|
||||
|
||||
typedef struct {
|
||||
|
||||
// This is our menu item map using the menuItem ID as a key
|
||||
// map[string]*MenuItem
|
||||
struct hashmap_s menuItems;
|
||||
|
||||
// This is our context menu map using the context menu ID as a key
|
||||
// map[string]*ContextMenu
|
||||
struct hashmap_s contextMenus;
|
||||
|
||||
// This is our tray menu map using the tray menu ID as a key
|
||||
// map[string]*TrayMenu
|
||||
struct hashmap_s trayMenus;
|
||||
|
||||
// Application Menu
|
||||
Menu* applicationMenu;
|
||||
|
||||
// Context menu data
|
||||
const char* contextMenuData;
|
||||
|
||||
} MenuManager;
|
||||
|
||||
|
||||
typedef struct {
|
||||
MenuManager* manager;
|
||||
const char *menuItemID;
|
||||
enum MenuItemType menuItemType;
|
||||
} MenuItemCallbackData;
|
||||
|
||||
typedef struct {
|
||||
const char *ID;
|
||||
const char* label;
|
||||
const char* alternateLabel;
|
||||
bool disabled;
|
||||
bool hidden;
|
||||
const char* colour;
|
||||
const char* font;
|
||||
int fontSize;
|
||||
const char* image;
|
||||
bool checked;
|
||||
|
||||
// Keyboard shortcut
|
||||
const char* acceleratorKey;
|
||||
const char** modifiers;
|
||||
|
||||
// Type of menuItem
|
||||
enum MenuItemType type;
|
||||
|
||||
// Indicates if the menuItem has a callback
|
||||
bool hasCallback;
|
||||
|
||||
// The platform specific menu data
|
||||
void* platformData;
|
||||
|
||||
// Submenu
|
||||
Menu* submenu;
|
||||
|
||||
// Radio group for this item
|
||||
vec_void_t radioGroup;
|
||||
|
||||
// Data for handling callbacks
|
||||
MenuItemCallbackData *callbackData;
|
||||
|
||||
} MenuItem;
|
||||
|
||||
// MenuItem
|
||||
MenuItem* NewMenuItem(enum MenuItemType type, const char* ID, const char* label, const char* alternateLabel, bool disabled, bool hidden, bool checked, const char* colour, const char* font, int fontsize, const char* image, const char* acceleratorKey, const char** modifiers, bool hasCallback, Menu* submenu);
|
||||
void DeleteMenuItem(MenuItem* menuItem);
|
||||
JsonNode* MenuItemAsJSONObject(MenuItem* menuItem);
|
||||
|
||||
// Menu
|
||||
Menu* NewMenu(JsonNode* menuData, JsonNode* radioData, MenuManager* menuManager);
|
||||
void DeleteMenu(Menu* menu);
|
||||
const char* MenuAsJSON(Menu* menu);
|
||||
JsonNode* MenuAsJSONObject(Menu* menu);
|
||||
void AddMenuItemToMenu(Menu* menu, MenuItem* menuItem, MenuManager* manager);
|
||||
MenuItem* processMenuItem(Menu *menu, JsonNode *item, MenuManager *manager);
|
||||
void processRadioGroup(Menu *menu, JsonNode *radioGroup, MenuManager* manager);
|
||||
|
||||
// Tray
|
||||
TrayMenu* NewTrayMenu(JsonNode* trayJSON, MenuManager *manager);
|
||||
void DeleteTrayMenu(TrayMenu *trayMenu);
|
||||
const char* TrayMenuAsJSON(TrayMenu* menu);
|
||||
|
||||
// MenuManager
|
||||
MenuManager* NewMenuManager();
|
||||
void DeleteMenuManager(MenuManager* manager);
|
||||
TrayMenu* AddTrayMenuToManager(MenuManager* manager, const char* trayMenuJSON);
|
||||
void ShowTrayMenus(MenuManager* manager);
|
||||
|
||||
// Callbacks
|
||||
MenuItemCallbackData* NewMenuItemCallbackData(MenuManager *manager, const char* menuItemID, enum MenuItemType menuItemType);
|
||||
void DeleteMenuItemCallbackData(MenuItemCallbackData* callbackData);
|
||||
const char* CreateMenuClickedMessage(const char *menuItemID, const char *data);
|
||||
|
||||
// Platform
|
||||
void SetupMenuPlatformData(Menu* menu);
|
||||
void DeleteMenuPlatformData(Menu* menu);
|
||||
void SetupMenuItemPlatformData(MenuItem* menuItem);
|
||||
void DeleteMenuItemPlatformData(MenuItem* menuItem);
|
||||
void SetupTrayMenuPlatformData(TrayMenu* menu);
|
||||
void DeleteTrayMenuPlatformData(TrayMenu* menu);
|
||||
void PlatformAddMenuItemToMenu(Menu *menu, MenuItem* menuItem, MenuManager* manager);
|
||||
void PlatformUpdateTrayIcon(TrayMenu *menu);
|
||||
|
||||
// Platform specific methods
|
||||
void UnloadTrayIcons();
|
||||
void LoadTrayIcons();
|
||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||
|
||||
#endif //MENU_H
|
||||
@@ -1,177 +1,35 @@
|
||||
//
|
||||
// Created by Lea Anthony on 6/1/21.
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#include "ffenestri.h"
|
||||
#include "ffenestri_darwin.h"
|
||||
#include "menu_darwin.h"
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "common.h"
|
||||
#include "menu.h"
|
||||
#include "trayicons.h"
|
||||
|
||||
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
|
||||
Menu* NewMenu(JsonNode *menuData) {
|
||||
// A cache for all our tray menu icons
|
||||
// Global because it's a singleton
|
||||
struct hashmap_s trayIconCache;
|
||||
|
||||
Menu *result = malloc(sizeof(Menu));
|
||||
|
||||
result->processedMenu = menuData;
|
||||
|
||||
// No title by default
|
||||
result->title = "";
|
||||
|
||||
// Initialise menuCallbackDataCache
|
||||
vec_init(&result->callbackDataCache);
|
||||
|
||||
// Allocate MenuItem Map
|
||||
if( 0 != hashmap_create((const unsigned)16, &result->menuItemMap)) {
|
||||
ABORT("[NewMenu] Not enough memory to allocate menuItemMap!");
|
||||
}
|
||||
// Allocate the Radio Group Map
|
||||
if( 0 != hashmap_create((const unsigned)4, &result->radioGroupMap)) {
|
||||
ABORT("[NewMenu] Not enough memory to allocate radioGroupMap!");
|
||||
}
|
||||
|
||||
// Init other members
|
||||
result->menu = NULL;
|
||||
result->parentData = NULL;
|
||||
|
||||
return result;
|
||||
void SetupMenuPlatformData(Menu* menu) {
|
||||
MacMenu* result = NEW(MacMenu);
|
||||
result->Menu = ALLOC("NSMenu");
|
||||
msg(result->Menu, s("initWithTitle:"), str(menu->label));
|
||||
msg(result->Menu, s("setAutoenablesItems:"), NO);
|
||||
menu->platformData = (void*)result;
|
||||
}
|
||||
|
||||
Menu* NewApplicationMenu(const char *menuAsJSON) {
|
||||
void DeleteMenuPlatformData(Menu* menu) {
|
||||
|
||||
// Parse the menu json
|
||||
JsonNode *processedMenu = json_decode(menuAsJSON);
|
||||
if( processedMenu == NULL ) {
|
||||
// Parse error!
|
||||
ABORT("Unable to parse Menu JSON: %s", menuAsJSON);
|
||||
}
|
||||
// Return if null
|
||||
if( menu == NULL || menu->platformData == NULL) return;
|
||||
|
||||
Menu *result = NewMenu(processedMenu);
|
||||
result->menuType = ApplicationMenuType;
|
||||
return result;
|
||||
MacMenu* macMenu = (MacMenu*) menu->platformData;
|
||||
RELEASE(macMenu->Menu);
|
||||
macMenu->Menu = NULL;
|
||||
}
|
||||
|
||||
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType) {
|
||||
MenuItemCallbackData* result = malloc(sizeof(MenuItemCallbackData));
|
||||
|
||||
result->menu = menu;
|
||||
result->menuID = menuID;
|
||||
result->menuItem = menuItem;
|
||||
result->menuItemType = menuItemType;
|
||||
|
||||
// Store reference to this so we can destroy later
|
||||
vec_push(&menu->callbackDataCache, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeleteMenu(Menu *menu) {
|
||||
|
||||
// Free menu item hashmap
|
||||
hashmap_destroy(&menu->menuItemMap);
|
||||
|
||||
// Free radio group members
|
||||
if( hashmap_num_entries(&menu->radioGroupMap) > 0 ) {
|
||||
if (0 != hashmap_iterate_pairs(&menu->radioGroupMap, freeHashmapItem, NULL)) {
|
||||
ABORT("[DeleteMenu] Failed to release radioGroupMap entries!");
|
||||
}
|
||||
}
|
||||
|
||||
// Free radio groups hashmap
|
||||
hashmap_destroy(&menu->radioGroupMap);
|
||||
|
||||
// Free up the processed menu memory
|
||||
if (menu->processedMenu != NULL) {
|
||||
json_delete(menu->processedMenu);
|
||||
menu->processedMenu = NULL;
|
||||
}
|
||||
|
||||
// Release the vector memory
|
||||
vec_deinit(&menu->callbackDataCache);
|
||||
|
||||
// Free nsmenu if we have it
|
||||
if ( menu->menu != NULL ) {
|
||||
msg(menu->menu, s("release"));
|
||||
}
|
||||
|
||||
free(menu);
|
||||
}
|
||||
|
||||
// Creates a JSON message for the given menuItemID and data
|
||||
const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID) {
|
||||
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
if (menuItemID == NULL ) {
|
||||
ABORT("Item ID NULL for menu!!\n");
|
||||
}
|
||||
json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID));
|
||||
json_append_member(jsonObject, "menuType", json_mkstring(MenuTypeAsString[(int)menuType]));
|
||||
if (data != NULL) {
|
||||
json_append_member(jsonObject, "data", json_mkstring(data));
|
||||
}
|
||||
if (parentID != NULL) {
|
||||
json_append_member(jsonObject, "parentID", json_mkstring(parentID));
|
||||
}
|
||||
const char *payload = json_encode(jsonObject);
|
||||
json_delete(jsonObject);
|
||||
const char *result = concat("MC", payload);
|
||||
MEMFREE(payload);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Callback for text menu items
|
||||
void menuItemCallback(id self, SEL cmd, id sender) {
|
||||
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *message;
|
||||
|
||||
// Update checkbox / radio item
|
||||
if( callbackData->menuItemType == Checkbox) {
|
||||
// Toggle state
|
||||
bool state = msg(callbackData->menuItem, s("state"));
|
||||
msg(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||
} else if( callbackData->menuItemType == Radio ) {
|
||||
// Check the menu items' current state
|
||||
bool selected = msg(callbackData->menuItem, s("state"));
|
||||
|
||||
// If it's already selected, exit early
|
||||
if (selected) return;
|
||||
|
||||
// Get this item's radio group members and turn them off
|
||||
id *members = (id*)hashmap_get(&(callbackData->menu->radioGroupMap), (char*)callbackData->menuID, strlen(callbackData->menuID));
|
||||
|
||||
// Uncheck all members of the group
|
||||
id thisMember = members[0];
|
||||
int count = 0;
|
||||
while(thisMember != NULL) {
|
||||
msg(thisMember, s("setState:"), NSControlStateValueOff);
|
||||
count = count + 1;
|
||||
thisMember = members[count];
|
||||
}
|
||||
|
||||
// check the selected menu item
|
||||
msg(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
|
||||
}
|
||||
|
||||
const char *menuID = callbackData->menuID;
|
||||
const char *data = NULL;
|
||||
enum MenuType menuType = callbackData->menu->menuType;
|
||||
const char *parentID = NULL;
|
||||
|
||||
// Generate message to send to backend
|
||||
if( menuType == ContextMenuType ) {
|
||||
// Get the context menu data from the menu
|
||||
ContextMenu* contextMenu = (ContextMenu*) callbackData->menu->parentData;
|
||||
data = contextMenu->contextMenuData;
|
||||
parentID = contextMenu->ID;
|
||||
} else if ( menuType == TrayMenuType ) {
|
||||
parentID = (const char*) callbackData->menu->parentData;
|
||||
}
|
||||
|
||||
message = createMenuClickedMessage(menuID, data, menuType, parentID);
|
||||
|
||||
// Notify the backend
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
id processAcceleratorKey(const char *key) {
|
||||
|
||||
@@ -343,161 +201,6 @@ id processAcceleratorKey(const char *key) {
|
||||
return str(key);
|
||||
}
|
||||
|
||||
|
||||
void addSeparator(id menu) {
|
||||
id item = msg(c("NSMenuItem"), s("separatorItem"));
|
||||
msg(menu, s("addItem:"), item);
|
||||
}
|
||||
|
||||
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
return item;
|
||||
}
|
||||
|
||||
id createMenuItem(id title, const char *action, const char *key) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
msg(item, s("autorelease"));
|
||||
return item;
|
||||
}
|
||||
|
||||
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
|
||||
id item = createMenuItem(str(title), action, key);
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(menu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id createMenu(id title) {
|
||||
id menu = ALLOC("NSMenu");
|
||||
msg(menu, s("initWithTitle:"), title);
|
||||
msg(menu, s("setAutoenablesItems:"), NO);
|
||||
// msg(menu, s("autorelease"));
|
||||
return menu;
|
||||
}
|
||||
|
||||
void createDefaultAppMenu(id parentMenu) {
|
||||
// App Menu
|
||||
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
|
||||
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
|
||||
id appMenu = createMenu(appName);
|
||||
|
||||
msg(appMenuItem, s("setSubmenu:"), appMenu);
|
||||
msg(parentMenu, s("addItem:"), appMenuItem);
|
||||
|
||||
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
|
||||
id item = createMenuItem(title, "hide:", "h");
|
||||
msg(appMenu, s("addItem:"), item);
|
||||
|
||||
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
|
||||
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
|
||||
addSeparator(appMenu);
|
||||
|
||||
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
|
||||
item = createMenuItem(title, "terminate:", "q");
|
||||
msg(appMenu, s("addItem:"), item);
|
||||
}
|
||||
|
||||
void createDefaultEditMenu(id parentMenu) {
|
||||
// Edit Menu
|
||||
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
|
||||
id editMenu = createMenu(str("Edit"));
|
||||
|
||||
msg(editMenuItem, s("setSubmenu:"), editMenu);
|
||||
msg(parentMenu, s("addItem:"), editMenuItem);
|
||||
|
||||
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
|
||||
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
|
||||
addSeparator(editMenu);
|
||||
addMenuItem(editMenu, "Cut", "cut:", "x", FALSE);
|
||||
addMenuItem(editMenu, "Copy", "copy:", "c", FALSE);
|
||||
addMenuItem(editMenu, "Paste", "paste:", "v", FALSE);
|
||||
addMenuItem(editMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
}
|
||||
|
||||
void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
const char *roleName = item->string_;
|
||||
|
||||
if ( STREQ(roleName, "appMenu") ) {
|
||||
createDefaultAppMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "editMenu")) {
|
||||
createDefaultEditMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hide")) {
|
||||
addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hideothers")) {
|
||||
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "unhide")) {
|
||||
addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "front")) {
|
||||
addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "undo")) {
|
||||
addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "redo")) {
|
||||
addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "cut")) {
|
||||
addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "copy")) {
|
||||
addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "paste")) {
|
||||
addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "delete")) {
|
||||
addMenuItem(parentMenu, "Delete", "delete:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if( STREQ(roleName, "pasteandmatchstyle")) {
|
||||
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
|
||||
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
||||
}
|
||||
if ( STREQ(roleName, "selectall")) {
|
||||
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "minimize")) {
|
||||
addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "zoom")) {
|
||||
addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "quit")) {
|
||||
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "togglefullscreen")) {
|
||||
addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This converts a string array of modifiers into the
|
||||
// equivalent MacOS Modifier Flags
|
||||
unsigned long parseModifiers(const char **modifiers) {
|
||||
@@ -530,356 +233,286 @@ unsigned long parseModifiers(const char **modifiers) {
|
||||
return result;
|
||||
}
|
||||
|
||||
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey) {
|
||||
|
||||
|
||||
void SetupMenuItemPlatformData(MenuItem* menuItem) {
|
||||
|
||||
// Create the platform data
|
||||
MacMenuItem *macMenuItem = NEW(MacMenuItem);
|
||||
|
||||
menuItem->platformData = macMenuItem;
|
||||
|
||||
// Create the NSMenuItem
|
||||
id item = ALLOC("NSMenuItem");
|
||||
macMenuItem->MenuItem = item;
|
||||
|
||||
// Store the item in the menu item map
|
||||
hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
|
||||
// // TODO: Process ROLE
|
||||
// if( menuItem->role != NULL ) {
|
||||
//
|
||||
// }
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key) {
|
||||
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Store the item in the menu item map
|
||||
hashmap_put(&menu->menuItemMap, (char*)menuid, strlen(menuid), item);
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
id key = processAcceleratorKey(menuItem->acceleratorKey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(menuItem->label),
|
||||
s("menuItemCallback:"), key);
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg(item, s("setToolTip:"), str(tooltip));
|
||||
}
|
||||
|
||||
// Process image
|
||||
if( image != NULL && strlen(image) > 0) {
|
||||
id data = ALLOC("NSData");
|
||||
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
|
||||
id nsimage = ALLOC("NSImage");
|
||||
msg(nsimage, s("initWithData:"), imageData);
|
||||
msg(item, s("setImage:"), nsimage);
|
||||
}
|
||||
|
||||
// Process Menu Item attributes
|
||||
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
||||
|
||||
// Process font
|
||||
id font;
|
||||
CGFloat fontSizeFloat = (CGFloat)fontSize;
|
||||
|
||||
// Check if valid
|
||||
id fontNameAsNSString = str(fontName);
|
||||
id fontsOnSystem = msg(msg(c("NSFontManager"), s("sharedFontManager")), s("availableFonts"));
|
||||
bool valid = msg(fontsOnSystem, s("containsObject:"), fontNameAsNSString);
|
||||
if( valid ) {
|
||||
font = msg(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
||||
} else {
|
||||
bool supportsMonospacedDigitSystemFont = (bool) msg(c("NSFont"), s("respondsToSelector:"), s("monospacedDigitSystemFontOfSize:weight:"));
|
||||
if( supportsMonospacedDigitSystemFont ) {
|
||||
font = msg(c("NSFont"), s("monospacedDigitSystemFontOfSize:weight:"), fontSizeFloat, NSFontWeightRegular);
|
||||
} else {
|
||||
font = msg(c("NSFont"), s("menuFontOfSize:"), fontSizeFloat);
|
||||
}
|
||||
}
|
||||
|
||||
// Add font to dictionary
|
||||
msg(dictionary, s("setObject:forKey:"), font, lookupStringConstant(str("NSFontAttributeName")));
|
||||
|
||||
// Add offset to dictionary
|
||||
id offset = msg(c("NSNumber"), s("numberWithFloat:"), 0.0);
|
||||
msg(dictionary, s("setObject:forKey:"), offset, lookupStringConstant(str("NSBaselineOffsetAttributeName")));
|
||||
|
||||
// RGBA
|
||||
if( RGBA != NULL && strlen(RGBA) > 0) {
|
||||
unsigned short r, g, b, a;
|
||||
|
||||
// white by default
|
||||
r = g = b = a = 255;
|
||||
int count = sscanf(RGBA, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
||||
if (count > 0) {
|
||||
id colour = msg(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||
(float)r / 255.0,
|
||||
(float)g / 255.0,
|
||||
(float)b / 255.0,
|
||||
(float)a / 255.0);
|
||||
msg(dictionary, s("setObject:forKey:"), colour, lookupStringConstant(str("NSForegroundColorAttributeName")));
|
||||
msg(colour, s("release"));
|
||||
}
|
||||
}
|
||||
|
||||
id attributedString = ALLOC("NSMutableAttributedString");
|
||||
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
|
||||
msg(dictionary, s("release"));
|
||||
|
||||
msg(item, s("setAttributedTitle:"), attributedString);
|
||||
msg(attributedString, s("autorelease"));
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("setEnabled:"), !menuItem->disabled);
|
||||
msg(item, s("autorelease"));
|
||||
|
||||
// Process modifiers
|
||||
if( modifiers != NULL ) {
|
||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||
if( menuItem->modifiers != NULL ) {
|
||||
unsigned long modifierFlags = parseModifiers(menuItem->modifiers);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
menuItem->modifiers = NULL;
|
||||
}
|
||||
msg(parentMenu, s("addItem:"), item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
void DeleteMenuItemPlatformData(MenuItem* menuItem) {
|
||||
|
||||
// Check if this item is hidden and if so, exit early!
|
||||
bool hidden = false;
|
||||
getJSONBool(item, "Hidden", &hidden);
|
||||
if( hidden ) {
|
||||
if( menuItem == NULL || menuItem->platformData == NULL) return;
|
||||
|
||||
MacMenuItem* macMenuItem = (MacMenuItem*) menuItem->platformData;
|
||||
RELEASE(macMenuItem->MenuItem);
|
||||
|
||||
MEMFREE(macMenuItem);
|
||||
|
||||
}
|
||||
|
||||
void PlatformAddMenuItemToMenu(Menu *menu, MenuItem* menuItem, MenuManager* manager) {
|
||||
|
||||
// Return if null
|
||||
if( menu == NULL || menu->platformData == NULL) return;
|
||||
if( menuItem == NULL || menuItem->platformData == NULL) return;
|
||||
|
||||
// Don't add if the item is hidden
|
||||
if( menuItem->hidden ) return;
|
||||
|
||||
// Setup callback
|
||||
if( menuItem->hasCallback ) {
|
||||
// Create a MenuItemCallbackData
|
||||
MacMenuItem* macMenuItem = (MacMenuItem*) menuItem->platformData;
|
||||
menuItem->callbackData = NewMenuItemCallbackData(manager, menuItem->ID, Text);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), menuItem->callbackData);
|
||||
msg(macMenuItem->MenuItem, s("setRepresentedObject:"), wrappedId);
|
||||
}
|
||||
|
||||
MacMenu* macMenu = (MacMenu*) menu->platformData;
|
||||
MacMenuItem* macMenuItem = (MacMenuItem*) menuItem->platformData;
|
||||
|
||||
msg(macMenu->Menu, s("addItem:"), macMenuItem->MenuItem);
|
||||
|
||||
}
|
||||
|
||||
void SetupTrayMenuPlatformData(TrayMenu* menu) {
|
||||
MacTrayMenu* result = NEW(MacTrayMenu);
|
||||
result->statusBarItem = NULL;
|
||||
|
||||
// TODO: Work out how to make this customisable
|
||||
result->iconPosition = NSImageLeft;
|
||||
|
||||
menu->platformData = (void*)result;
|
||||
}
|
||||
|
||||
void DeleteTrayMenuPlatformData(TrayMenu* menu) {
|
||||
|
||||
// Return if null
|
||||
if( menu == NULL || menu->platformData == NULL) return;
|
||||
|
||||
MacTrayMenu* macMenu = (MacTrayMenu*) menu->platformData;
|
||||
RELEASE(macMenu->statusBarItem);
|
||||
macMenu->statusBarItem = NULL;
|
||||
}
|
||||
|
||||
|
||||
void PlatformUpdateTrayIcon(TrayMenu* trayMenu) {
|
||||
|
||||
MacTrayMenu* macTrayMenu = (MacTrayMenu*) trayMenu->platformData;
|
||||
|
||||
id statusBarButton = msg(macTrayMenu->statusBarItem, s("button"));
|
||||
|
||||
// Empty icon means remove it
|
||||
if (trayMenu->Icon == NULL || strlen(trayMenu->Icon) == 0) {
|
||||
msg(statusBarButton, s("setImage:"), NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the role
|
||||
JsonNode *role = json_find_member(item, "Role");
|
||||
if( role != NULL ) {
|
||||
processMenuRole(menu, parentMenu, role);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a submenu
|
||||
JsonNode *submenu = json_find_member(item, "SubMenu");
|
||||
if( submenu != NULL ) {
|
||||
// Get the label
|
||||
JsonNode *menuNameNode = json_find_member(item, "Label");
|
||||
const char *name = "";
|
||||
if ( menuNameNode != NULL) {
|
||||
name = menuNameNode->string_;
|
||||
}
|
||||
|
||||
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||
id thisMenu = createMenu(str(name));
|
||||
|
||||
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||
msg(parentMenu, s("addItem:"), thisMenuItem);
|
||||
|
||||
JsonNode *submenuItems = json_find_member(submenu, "Items");
|
||||
// If we have no items, just return
|
||||
if ( submenuItems == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop over submenu items
|
||||
JsonNode *item;
|
||||
json_foreach(item, submenuItems) {
|
||||
// Get item label
|
||||
processMenuItem(menu, thisMenu, item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a user menu. Get the common data
|
||||
// Get the label
|
||||
const char *label = getJSONString(item, "Label");
|
||||
if ( label == NULL) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
const char *menuid = getJSONString(item, "ID");
|
||||
if ( menuid == NULL) {
|
||||
menuid = "";
|
||||
}
|
||||
|
||||
bool disabled = false;
|
||||
getJSONBool(item, "Disabled", &disabled);
|
||||
|
||||
// Get the Accelerator
|
||||
JsonNode *accelerator = json_find_member(item, "Accelerator");
|
||||
const char *acceleratorkey = NULL;
|
||||
const char **modifiers = NULL;
|
||||
|
||||
const char *tooltip = getJSONString(item, "Tooltip");
|
||||
const char *image = getJSONString(item, "Image");
|
||||
const char *fontName = getJSONString(item, "FontName");
|
||||
const char *RGBA = getJSONString(item, "RGBA");
|
||||
int fontSize = 12;
|
||||
getJSONInt(item, "FontSize", &fontSize);
|
||||
|
||||
// If we have an accelerator
|
||||
if( accelerator != NULL ) {
|
||||
// Get the key
|
||||
acceleratorkey = getJSONString(accelerator, "Key");
|
||||
// Check if there are modifiers
|
||||
JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
|
||||
if ( modifiersList != NULL ) {
|
||||
// Allocate an array of strings
|
||||
int noOfModifiers = json_array_length(modifiersList);
|
||||
|
||||
// Do we have any?
|
||||
if (noOfModifiers > 0) {
|
||||
modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
|
||||
JsonNode *modifier;
|
||||
int count = 0;
|
||||
// Iterate the modifiers and save a reference to them in our new array
|
||||
json_foreach(modifier, modifiersList) {
|
||||
// Get modifier name
|
||||
modifiers[count] = modifier->string_;
|
||||
count++;
|
||||
}
|
||||
// Null terminate the modifier list
|
||||
modifiers[count] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the Type
|
||||
JsonNode *type = json_find_member(item, "Type");
|
||||
if( type != NULL ) {
|
||||
|
||||
if( STREQ(type->string_, "Text")) {
|
||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Separator")) {
|
||||
addSeparator(parentMenu);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Checkbox")) {
|
||||
// Get checked state
|
||||
bool checked = false;
|
||||
getJSONBool(item, "Checked", &checked);
|
||||
|
||||
processCheckboxMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
|
||||
}
|
||||
else if ( STREQ(type->string_, "Radio")) {
|
||||
// Get checked state
|
||||
bool checked = false;
|
||||
getJSONBool(item, "Checked", &checked);
|
||||
|
||||
processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( modifiers != NULL ) {
|
||||
free(modifiers);
|
||||
}
|
||||
|
||||
return;
|
||||
id trayImage = HASHMAP_GET(trayIconCache, trayMenu->Icon);
|
||||
msg(statusBarButton, s("setImagePosition:"), macTrayMenu->iconPosition);
|
||||
msg(statusBarButton, s("setImage:"), trayImage);
|
||||
}
|
||||
|
||||
void processMenuData(Menu *menu, JsonNode *menuData) {
|
||||
JsonNode *items = json_find_member(menuData, "Items");
|
||||
if( items == NULL ) {
|
||||
// Parse error!
|
||||
ABORT("Unable to find 'Items' in menu JSON!");
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->Label == NULL ) return;
|
||||
|
||||
// Update button label
|
||||
MacTrayMenu* macTrayMenu = (MacTrayMenu*) trayMenu->platformData;
|
||||
id statusBarButton = msg(macTrayMenu->statusBarItem, s("button"));
|
||||
msg(statusBarButton, s("setTitle:"), str(label));
|
||||
}
|
||||
|
||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
if( trayMenu == NULL || trayMenu->platformData == NULL ) return;
|
||||
|
||||
MacTrayMenu* macTrayMenu = (MacTrayMenu*) trayMenu->platformData;
|
||||
|
||||
// Create a status bar item if we don't have one
|
||||
if( macTrayMenu->statusBarItem == NULL ) {
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
macTrayMenu->statusBarItem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||
msg(macTrayMenu->statusBarItem, s("retain"));
|
||||
}
|
||||
|
||||
// Iterate items
|
||||
JsonNode *item;
|
||||
json_foreach(item, items) {
|
||||
// Process each menu item
|
||||
processMenuItem(menu, menu->menu, item);
|
||||
id statusBarButton = msg(macTrayMenu->statusBarItem, s("button"));
|
||||
msg(statusBarButton, s("setImagePosition:"), macTrayMenu->iconPosition);
|
||||
|
||||
// Update the icon if needed
|
||||
PlatformUpdateTrayIcon(trayMenu);
|
||||
|
||||
// Update the label if needed
|
||||
UpdateTrayLabel(trayMenu, trayMenu->Label);
|
||||
|
||||
// If we don't have a menu, return
|
||||
if( trayMenu->Menu == NULL ) return;
|
||||
|
||||
// Update the menu
|
||||
MacMenu* macMenu = (MacMenu*) trayMenu->Menu->platformData;
|
||||
msg(macTrayMenu->statusBarItem, s("setMenu:"), macMenu->Menu);
|
||||
}
|
||||
|
||||
void LoadTrayIcons() {
|
||||
|
||||
// Allocate the Tray Icons
|
||||
if( 0 != hashmap_create((const unsigned)4, &trayIconCache)) {
|
||||
// Couldn't allocate map
|
||||
ABORT("Not enough memory to allocate trayIconCache!");
|
||||
}
|
||||
|
||||
unsigned int count = 0;
|
||||
while( 1 ) {
|
||||
const unsigned char *name = trayIcons[count++];
|
||||
if( name == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
const unsigned char *lengthAsString = trayIcons[count++];
|
||||
if( name == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
const unsigned char *data = trayIcons[count++];
|
||||
if( data == 0x00 ) {
|
||||
break;
|
||||
}
|
||||
char *c;
|
||||
unsigned long length = strtol((const char *)lengthAsString, &c, 10);
|
||||
|
||||
// Create the icon and add to the hashmap
|
||||
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
|
||||
id trayImage = ALLOC("NSImage");
|
||||
msg(trayImage, s("initWithData:"), imageData);
|
||||
HASHMAP_PUT(trayIconCache, (const char *)name, trayImage);
|
||||
}
|
||||
}
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) {
|
||||
|
||||
int groupLength;
|
||||
getJSONInt(radioGroup, "Length", &groupLength);
|
||||
JsonNode *members = json_find_member(radioGroup, "Members");
|
||||
JsonNode *member;
|
||||
|
||||
// Allocate array
|
||||
size_t arrayLength = sizeof(id)*(groupLength+1);
|
||||
id memberList[arrayLength];
|
||||
|
||||
// Build the radio group items
|
||||
int count=0;
|
||||
json_foreach(member, members) {
|
||||
// Get menu by id
|
||||
id menuItem = (id)hashmap_get(&menu->menuItemMap, (char*)member->string_, strlen(member->string_));
|
||||
// Save Member
|
||||
memberList[count] = menuItem;
|
||||
count = count + 1;
|
||||
}
|
||||
// Null terminate array
|
||||
memberList[groupLength] = 0;
|
||||
|
||||
// Store the members
|
||||
json_foreach(member, members) {
|
||||
// Copy the memberList
|
||||
char *newMemberList = (char *)malloc(arrayLength);
|
||||
memcpy(newMemberList, memberList, arrayLength);
|
||||
// add group to each member of group
|
||||
hashmap_put(&menu->radioGroupMap, member->string_, strlen(member->string_), newMemberList);
|
||||
}
|
||||
|
||||
void UnloadTrayIcons() {
|
||||
// Release the tray cache images
|
||||
HASHMAP_ITERATE(trayIconCache, releaseNSObject, NULL);
|
||||
HASHMAP_DESTROY(trayIconCache);
|
||||
}
|
||||
//
|
||||
//// Callback for text menu items
|
||||
//void menuItemCallback(id self, SEL cmd, id sender) {
|
||||
// MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
// if( callbackData == NULL) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// struct TrayMenuStore *store = callbackData->store;
|
||||
// const char* menuItemID = callbackData->menuItemID;
|
||||
// id nsmenu = GetMenuItemFromStore((TrayMenuStore *) store, menuItemID);
|
||||
// if ( nsmenu == NULL ) {
|
||||
// // The menu has been deleted!
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const char *message;
|
||||
//
|
||||
// // Update checkbox / radio item
|
||||
// if( callbackData->menuItemType == Checkbox) {
|
||||
// // Toggle state
|
||||
// bool state = msg(nsmenu, s("state"));
|
||||
// msg(nsmenu, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||
// } else if( callbackData->menuItemType == Radio ) {
|
||||
// // Check the menu items' current state
|
||||
// bool selected = msg(nsmenu, s("state"));
|
||||
//
|
||||
// // If it's already selected, exit early
|
||||
// if (selected) return;
|
||||
//
|
||||
// // Get this item's radio group members and turn them off
|
||||
// id *members = (id*)hashmap_get(&(callbackData->menu->radioGroupMap), (char*)callbackData->menuItemID, strlen(callbackData->menuItemID));
|
||||
//
|
||||
// // Uncheck all members of the group
|
||||
// id thisMember = members[0];
|
||||
// int count = 0;
|
||||
// while(thisMember != NULL) {
|
||||
// msg(thisMember, s("setState:"), NSControlStateValueOff);
|
||||
// count = count + 1;
|
||||
// thisMember = members[count];
|
||||
// }
|
||||
//
|
||||
// // check the selected menu item
|
||||
// msg(nsmenu, s("setState:"), NSControlStateValueOn);
|
||||
// }
|
||||
//
|
||||
// message = createMenuClickedMessage(menuItemID, GetContextMenuDataFromStore((TrayMenuStore *) store));
|
||||
//
|
||||
// // Notify the backend
|
||||
// messageFromWindowCallback(message);
|
||||
// MEMFREE(message);
|
||||
//}
|
||||
|
||||
id GetMenu(Menu *menu) {
|
||||
void PlatformMenuItemCallback(id self, SEL cmd, id sender) {
|
||||
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
||||
const char *message;
|
||||
|
||||
// Pull out the menu data
|
||||
JsonNode *menuData = json_find_member(menu->processedMenu, "Menu");
|
||||
if( menuData == NULL ) {
|
||||
ABORT("Unable to find Menu data: %s", menu->processedMenu);
|
||||
MenuManager* manager = callbackData->manager;
|
||||
MenuItem* menuItem = HASHMAP_GET(manager->menuItems, callbackData->menuItemID);
|
||||
|
||||
if( menuItem == NULL ) return;
|
||||
|
||||
MacMenuItem *macMenuItem = menuItem->platformData;
|
||||
id nsMenuItem = macMenuItem->MenuItem;
|
||||
|
||||
// Update checkbox / radio item
|
||||
if( callbackData->menuItemType == Checkbox) {
|
||||
// Toggle state
|
||||
bool state = msg(nsMenuItem, s("state"));
|
||||
msg(nsMenuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||
} else if( callbackData->menuItemType == Radio ) {
|
||||
// Check the menu items' current state
|
||||
bool selected = msg(nsMenuItem, s("state"));
|
||||
|
||||
// If it's already selected, exit early
|
||||
if (selected) return;
|
||||
|
||||
int i=0; const char *groupMenuItemID;
|
||||
vec_foreach(&menuItem->radioGroup, groupMenuItemID, i) {
|
||||
// Get member
|
||||
MenuItem* groupMember = HASHMAP_GET(manager->menuItems, groupMenuItemID);
|
||||
MacMenuItem* groupMacMenuItem = (MacMenuItem*) groupMember->platformData;
|
||||
msg(groupMacMenuItem->MenuItem, s("setState:"), NSControlStateValueOff);
|
||||
}
|
||||
|
||||
// check the selected menu item
|
||||
msg(nsMenuItem, s("setState:"), NSControlStateValueOn);
|
||||
}
|
||||
|
||||
menu->menu = createMenu(str(""));
|
||||
message = CreateMenuClickedMessage(callbackData->menuItemID, manager->contextMenuData);
|
||||
|
||||
// Process the menu data
|
||||
processMenuData(menu, menuData);
|
||||
|
||||
// Create the radiogroup cache
|
||||
JsonNode *radioGroups = json_find_member(menu->processedMenu, "RadioGroups");
|
||||
if( radioGroups == NULL ) {
|
||||
// Parse error!
|
||||
ABORT("Unable to find RadioGroups data: %s", menu->processedMenu);
|
||||
}
|
||||
|
||||
// Iterate radio groups
|
||||
JsonNode *radioGroup;
|
||||
json_foreach(radioGroup, radioGroups) {
|
||||
// Get item label
|
||||
processRadioGroupJSON(menu, radioGroup);
|
||||
}
|
||||
|
||||
return menu->menu;
|
||||
// Notify the backend
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,114 +1,21 @@
|
||||
//
|
||||
// Created by Lea Anthony on 6/1/21.
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#ifndef MENU_DARWIN_H
|
||||
#define MENU_DARWIN_H
|
||||
|
||||
#include "common.h"
|
||||
#include "ffenestri_darwin.h"
|
||||
|
||||
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2};
|
||||
enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
|
||||
static const char *MenuTypeAsString[] = {
|
||||
"ApplicationMenu", "ContextMenu", "TrayMenu",
|
||||
};
|
||||
|
||||
typedef struct _NSRange {
|
||||
unsigned long location;
|
||||
unsigned long length;
|
||||
} NSRange;
|
||||
|
||||
#define NSFontWeightUltraLight -0.8
|
||||
#define NSFontWeightThin -0.6
|
||||
#define NSFontWeightLight -0.4
|
||||
#define NSFontWeightRegular 0
|
||||
#define NSFontWeightMedium 0.23
|
||||
#define NSFontWeightSemibold 0.3
|
||||
#define NSFontWeightBold 0.4
|
||||
#define NSFontWeightHeavy 0.56
|
||||
#define NSFontWeightBlack 0.62
|
||||
|
||||
extern void messageFromWindowCallback(const char *);
|
||||
#include "menu.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
const char *title;
|
||||
|
||||
/*** Internal ***/
|
||||
|
||||
// The decoded version of the Menu JSON
|
||||
JsonNode *processedMenu;
|
||||
|
||||
struct hashmap_s menuItemMap;
|
||||
struct hashmap_s radioGroupMap;
|
||||
|
||||
// Vector to keep track of callback data memory
|
||||
vec_void_t callbackDataCache;
|
||||
|
||||
// The NSMenu for this menu
|
||||
id menu;
|
||||
|
||||
// The parent data, eg ContextMenuStore or Tray
|
||||
void *parentData;
|
||||
|
||||
// The commands for the menu callbacks
|
||||
const char *callbackCommand;
|
||||
|
||||
// This indicates if we are an Application Menu, tray menu or context menu
|
||||
enum MenuType menuType;
|
||||
|
||||
|
||||
} Menu;
|
||||
|
||||
id Menu;
|
||||
} MacMenu;
|
||||
|
||||
typedef struct {
|
||||
id menuItem;
|
||||
Menu *menu;
|
||||
const char *menuID;
|
||||
enum MenuItemType menuItemType;
|
||||
} MenuItemCallbackData;
|
||||
id MenuItem;
|
||||
} MacMenuItem;
|
||||
|
||||
typedef struct {
|
||||
id statusBarItem;
|
||||
int iconPosition;
|
||||
} MacTrayMenu;
|
||||
|
||||
|
||||
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
|
||||
Menu* NewMenu(JsonNode *menuData);
|
||||
|
||||
Menu* NewApplicationMenu(const char *menuAsJSON);
|
||||
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const char *menuID, enum MenuItemType menuItemType);
|
||||
|
||||
void DeleteMenu(Menu *menu);
|
||||
|
||||
// Creates a JSON message for the given menuItemID and data
|
||||
const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID);
|
||||
// Callback for text menu items
|
||||
void menuItemCallback(id self, SEL cmd, id sender);
|
||||
id processAcceleratorKey(const char *key);
|
||||
|
||||
|
||||
void addSeparator(id menu);
|
||||
id createMenuItemNoAutorelease( id title, const char *action, const char *key);
|
||||
|
||||
id createMenuItem(id title, const char *action, const char *key);
|
||||
|
||||
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled);
|
||||
|
||||
id createMenu(id title);
|
||||
void createDefaultAppMenu(id parentMenu);
|
||||
void createDefaultEditMenu(id parentMenu);
|
||||
|
||||
void processMenuRole(Menu *menu, id parentMenu, JsonNode *item);
|
||||
// This converts a string array of modifiers into the
|
||||
// equivalent MacOS Modifier Flags
|
||||
unsigned long parseModifiers(const char **modifiers);
|
||||
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey);
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA);
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
||||
void processMenuData(Menu *menu, JsonNode *menuData);
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
|
||||
id GetMenu(Menu *menu);
|
||||
#endif //ASSETS_C_MENU_DARWIN_H
|
||||
void PlatformMenuItemCallback(id self, SEL cmd, id sender);
|
||||
|
||||
542
v2/internal/ffenestri/menu_darwin_old.c
Normal file
542
v2/internal/ffenestri/menu_darwin_old.c
Normal file
@@ -0,0 +1,542 @@
|
||||
//
|
||||
// Created by Lea Anthony on 6/1/21.
|
||||
//
|
||||
|
||||
#include "ffenestri_darwin.h"
|
||||
#include "menu_darwin_old.h"
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "traymenustore_darwin.h"
|
||||
|
||||
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
|
||||
Menu* NewMenu(JsonNode *menuData, JsonNode *radioGroups, struct TrayMenuStore *store) {
|
||||
|
||||
Menu *result = malloc(sizeof(Menu));
|
||||
|
||||
// No title by default
|
||||
result->title = "";
|
||||
|
||||
// Initialise menuCallbackDataCache
|
||||
vec_init(&result->callbackDataCache);
|
||||
|
||||
// Allocate MenuItem Map
|
||||
if( 0 != hashmap_create((const unsigned)16, &result->menuItemMap)) {
|
||||
ABORT("[NewMenu] Not enough memory to allocate menuItemMap!");
|
||||
}
|
||||
// Allocate the Radio Group Map
|
||||
if( 0 != hashmap_create((const unsigned)4, &result->radioGroupMap)) {
|
||||
ABORT("[NewMenu] Not enough memory to allocate radioGroupMap!");
|
||||
}
|
||||
|
||||
// Init other members
|
||||
result->menu = NULL;
|
||||
result->store = store;
|
||||
|
||||
// Process the menu
|
||||
ProcessMenu(result, menuData, radioGroups);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Menu* NewApplicationMenu(const char *menuAsJSON, struct TrayMenuStore *store) {
|
||||
|
||||
// Parse the menu json
|
||||
JsonNode *processedMenu = json_decode(menuAsJSON);
|
||||
if( processedMenu == NULL ) {
|
||||
// Parse error!
|
||||
ABORT("Unable to parse Menu JSON: %s", menuAsJSON);
|
||||
}
|
||||
|
||||
// TODO - fixup
|
||||
Menu *result = NewMenu(processedMenu, NULL, store);
|
||||
result->menuType = ApplicationMenuType;
|
||||
return result;
|
||||
}
|
||||
|
||||
//TODO: Put traymenu store in callback instead of menu as it'll never be null
|
||||
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, const char *menuItemID, enum MenuItemType menuItemType) {
|
||||
MenuItemCallbackData* result = malloc(sizeof(MenuItemCallbackData));
|
||||
|
||||
result->store = menu->store;
|
||||
result->menuItemID = STRCOPY(menuItemID);
|
||||
result->menuItemType = menuItemType;
|
||||
|
||||
// Store reference to this so we can destroy later
|
||||
vec_push(&menu->callbackDataCache, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DeleteMenu(Menu *menu) {
|
||||
|
||||
if( menu == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Free menu item hashmap
|
||||
hashmap_destroy(&menu->menuItemMap);
|
||||
|
||||
// Free radio group members
|
||||
if( hashmap_num_entries(&menu->radioGroupMap) > 0 ) {
|
||||
if (0 != hashmap_iterate_pairs(&menu->radioGroupMap, freeHashmapItem, NULL)) {
|
||||
ABORT("[DeleteMenu] Failed to release radioGroupMap entries!");
|
||||
}
|
||||
}
|
||||
|
||||
// Free radio groups hashmap
|
||||
hashmap_destroy(&menu->radioGroupMap);
|
||||
|
||||
// Release the callback cache memory
|
||||
int i; MenuItemCallbackData *callback;
|
||||
vec_foreach(&menu->callbackDataCache, callback, i) {
|
||||
MEMFREE(callback->menuItemID);
|
||||
}
|
||||
vec_deinit(&menu->callbackDataCache);
|
||||
|
||||
// Free nsmenu if we have it
|
||||
if ( menu->menu != NULL ) {
|
||||
msg(menu->menu, s("release"));
|
||||
}
|
||||
|
||||
free(menu);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void addSeparator(id menu) {
|
||||
id item = msg(c("NSMenuItem"), s("separatorItem"));
|
||||
msg(menu, s("addItem:"), item);
|
||||
}
|
||||
|
||||
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
return item;
|
||||
}
|
||||
|
||||
id createMenuItem(id title, const char *action, const char *key) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||
msg(item, s("autorelease"));
|
||||
return item;
|
||||
}
|
||||
|
||||
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
|
||||
id item = createMenuItem(str(title), action, key);
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(menu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id createMenu(id title) {
|
||||
id menu = ALLOC("NSMenu");
|
||||
msg(menu, s("initWithTitle:"), title);
|
||||
msg(menu, s("setAutoenablesItems:"), NO);
|
||||
// msg(menu, s("autorelease"));
|
||||
return menu;
|
||||
}
|
||||
|
||||
void createDefaultAppMenu(id parentMenu) {
|
||||
// App Menu
|
||||
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
|
||||
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
|
||||
id appMenu = createMenu(appName);
|
||||
|
||||
msg(appMenuItem, s("setSubmenu:"), appMenu);
|
||||
msg(parentMenu, s("addItem:"), appMenuItem);
|
||||
|
||||
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
|
||||
id item = createMenuItem(title, "hide:", "h");
|
||||
msg(appMenu, s("addItem:"), item);
|
||||
|
||||
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
|
||||
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
|
||||
addSeparator(appMenu);
|
||||
|
||||
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
|
||||
item = createMenuItem(title, "terminate:", "q");
|
||||
msg(appMenu, s("addItem:"), item);
|
||||
}
|
||||
|
||||
void createDefaultEditMenu(id parentMenu) {
|
||||
// Edit Menu
|
||||
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
|
||||
id editMenu = createMenu(str("Edit"));
|
||||
|
||||
msg(editMenuItem, s("setSubmenu:"), editMenu);
|
||||
msg(parentMenu, s("addItem:"), editMenuItem);
|
||||
|
||||
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
|
||||
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
|
||||
addSeparator(editMenu);
|
||||
addMenuItem(editMenu, "Cut", "cut:", "x", FALSE);
|
||||
addMenuItem(editMenu, "Copy", "copy:", "c", FALSE);
|
||||
addMenuItem(editMenu, "Paste", "paste:", "v", FALSE);
|
||||
addMenuItem(editMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
}
|
||||
|
||||
void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
const char *roleName = item->string_;
|
||||
|
||||
if ( STREQ(roleName, "appMenu") ) {
|
||||
createDefaultAppMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "editMenu")) {
|
||||
createDefaultEditMenu(parentMenu);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hide")) {
|
||||
addMenuItem(parentMenu, "Hide Window", "hide:", "h", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "hideothers")) {
|
||||
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "unhide")) {
|
||||
addMenuItem(parentMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "front")) {
|
||||
addMenuItem(parentMenu, "Bring All to Front", "arrangeInFront:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "undo")) {
|
||||
addMenuItem(parentMenu, "Undo", "undo:", "z", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "redo")) {
|
||||
addMenuItem(parentMenu, "Redo", "redo:", "y", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "cut")) {
|
||||
addMenuItem(parentMenu, "Cut", "cut:", "x", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "copy")) {
|
||||
addMenuItem(parentMenu, "Copy", "copy:", "c", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "paste")) {
|
||||
addMenuItem(parentMenu, "Paste", "paste:", "v", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "delete")) {
|
||||
addMenuItem(parentMenu, "Delete", "delete:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if( STREQ(roleName, "pasteandmatchstyle")) {
|
||||
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
|
||||
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
||||
}
|
||||
if ( STREQ(roleName, "selectall")) {
|
||||
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "minimize")) {
|
||||
addMenuItem(parentMenu, "Minimize", "miniaturize:", "m", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "zoom")) {
|
||||
addMenuItem(parentMenu, "Zoom", "performZoom:", "", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "quit")) {
|
||||
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "togglefullscreen")) {
|
||||
addMenuItem(parentMenu, "Toggle Full Screen", "toggleFullScreen:", "f", FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuItemID, bool disabled, bool checked, const char *acceleratorkey, bool hascallback) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Store the item in the menu item map
|
||||
// hashmap_put(&menu->menuItemMap, (char*)menuItemID, strlen(menuItemID), item);
|
||||
|
||||
SaveMenuItemInStore((TrayMenuStore *) menu->store, menuItemID, item);
|
||||
|
||||
if( hascallback ) {
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, menuItemID, Radio);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
}
|
||||
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuItemID, bool disabled, bool checked, const char *key, bool hascallback) {
|
||||
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Store the item in the menu item map
|
||||
// hashmap_put(&menu->menuItemMap, (char*)menuItemID, strlen(menuItemID), item);
|
||||
SaveMenuItemInStore((TrayMenuStore *) menu->store, menuItemID, item);
|
||||
|
||||
if( hascallback ) {
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, menuItemID, Checkbox);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
}
|
||||
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||
msg(parentmenu, s("addItem:"), item);
|
||||
return item;
|
||||
}
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuItemID, bool disabled, const char *acceleratorkey, const char **modifiers, bool hascallback) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
SaveMenuItemInStore((TrayMenuStore *) menu->store, menuItemID, item);
|
||||
|
||||
if( hascallback ) {
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, menuItemID, Text);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
}
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s("menuItemCallback:"), key);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
|
||||
// Process modifiers
|
||||
if( modifiers != NULL ) {
|
||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
}
|
||||
msg(parentMenu, s("addItem:"), item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
|
||||
// Check if this item is hidden and if so, exit early!
|
||||
bool hidden = getJSONBool(item, "h");
|
||||
if( hidden ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the role
|
||||
JsonNode *role = json_find_member(item, "r");
|
||||
if( role != NULL ) {
|
||||
processMenuRole(menu, parentMenu, role);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a submenu
|
||||
JsonNode *submenu = json_find_member(item, "S");
|
||||
if( submenu != NULL ) {
|
||||
// Get the label
|
||||
JsonNode *menuNameNode = json_find_member(item, "l");
|
||||
const char *name = "";
|
||||
if ( menuNameNode != NULL) {
|
||||
name = menuNameNode->string_;
|
||||
}
|
||||
|
||||
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||
id thisMenu = createMenu(str(name));
|
||||
|
||||
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||
msg(parentMenu, s("addItem:"), thisMenuItem);
|
||||
|
||||
JsonNode *submenuItems = json_find_member(submenu, "i");
|
||||
// If we have no items, just return
|
||||
if ( submenuItems == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop over submenu items
|
||||
JsonNode *item;
|
||||
json_foreach(item, submenuItems) {
|
||||
// Get item label
|
||||
processMenuItem(menu, thisMenu, item);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a user menu. Get the common data
|
||||
// Get the label
|
||||
const char *label = getJSONString(item, "l");
|
||||
if ( label == NULL) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
const char *menuItemID = getJSONString(item, "I");
|
||||
if ( menuItemID == NULL) {
|
||||
menuItemID = "";
|
||||
}
|
||||
|
||||
bool disabled = getJSONBool(item, "d");
|
||||
|
||||
// Get the Accelerator
|
||||
JsonNode *accelerator = json_find_member(item, "a");
|
||||
const char *acceleratorkey = NULL;
|
||||
const char **modifiers = NULL;
|
||||
|
||||
// If we have an accelerator
|
||||
if( accelerator != NULL ) {
|
||||
// Get the key
|
||||
acceleratorkey = getJSONString(accelerator, "Key");
|
||||
// Check if there are modifiers
|
||||
JsonNode *modifiersList = json_find_member(accelerator, "Modifiers");
|
||||
if ( modifiersList != NULL ) {
|
||||
// Allocate an array of strings
|
||||
int noOfModifiers = json_array_length(modifiersList);
|
||||
|
||||
// Do we have any?
|
||||
if (noOfModifiers > 0) {
|
||||
modifiers = malloc(sizeof(const char *) * (noOfModifiers + 1));
|
||||
JsonNode *modifier;
|
||||
int count = 0;
|
||||
// Iterate the modifiers and save a reference to them in our new array
|
||||
json_foreach(modifier, modifiersList) {
|
||||
// Get modifier name
|
||||
modifiers[count] = modifier->string_;
|
||||
count++;
|
||||
}
|
||||
// Null terminate the modifier list
|
||||
modifiers[count] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// has callback?
|
||||
bool hascallback = getJSONBool(item, "C");
|
||||
|
||||
// Get the Type
|
||||
const char *type = mustJSONString(item, "t");
|
||||
|
||||
if( STREQ(type, "t")) {
|
||||
processTextMenuItem(menu, parentMenu, label, menuItemID, disabled, acceleratorkey, modifiers, hascallback);
|
||||
}
|
||||
else if ( STREQ(type, "s")) {
|
||||
addSeparator(parentMenu);
|
||||
}
|
||||
else if ( STREQ(type, "c")) {
|
||||
// Get checked state
|
||||
bool checked = getJSONBool(item, "c");
|
||||
|
||||
processCheckboxMenuItem(menu, parentMenu, label, menuItemID, disabled, checked, "", hascallback);
|
||||
}
|
||||
else if ( STREQ(type, "r")) {
|
||||
// Get checked state
|
||||
bool checked = getJSONBool(item, "c");
|
||||
|
||||
processRadioMenuItem(menu, parentMenu, label, menuItemID, disabled, checked, "", hascallback);
|
||||
}
|
||||
|
||||
|
||||
if ( modifiers != NULL ) {
|
||||
free(modifiers);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void processMenuData(Menu *menu, JsonNode *menuData) {
|
||||
// Iterate items
|
||||
JsonNode *item;
|
||||
json_foreach(item, menuData) {
|
||||
// Process each menu item
|
||||
processMenuItem(menu, menu->menu, item);
|
||||
}
|
||||
}
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) {
|
||||
|
||||
int groupLength;
|
||||
getJSONInt(radioGroup, "Length", &groupLength);
|
||||
JsonNode *members = json_find_member(radioGroup, "Members");
|
||||
JsonNode *member;
|
||||
|
||||
// Allocate array
|
||||
size_t arrayLength = sizeof(id)*(groupLength+1);
|
||||
id memberList[arrayLength];
|
||||
|
||||
// Build the radio group items
|
||||
int count=0;
|
||||
json_foreach(member, members) {
|
||||
// Get menu by id
|
||||
id menuItem = (id)hashmap_get(&menu->menuItemMap, (char*)member->string_, strlen(member->string_));
|
||||
// Save Member
|
||||
memberList[count] = menuItem;
|
||||
count = count + 1;
|
||||
}
|
||||
// Null terminate array
|
||||
memberList[groupLength] = 0;
|
||||
|
||||
// Store the members
|
||||
json_foreach(member, members) {
|
||||
// Copy the memberList
|
||||
char *newMemberList = (char *)malloc(arrayLength);
|
||||
memcpy(newMemberList, memberList, arrayLength);
|
||||
// add group to each member of group
|
||||
hashmap_put(&menu->radioGroupMap, member->string_, strlen(member->string_), newMemberList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
id ProcessMenu(Menu *menu, JsonNode *menuData, JsonNode *radioGroups) {
|
||||
|
||||
// exit if we have no meny
|
||||
if( menuData == NULL ) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
menu->menu = createMenu(str(""));
|
||||
|
||||
// Process the menu data
|
||||
processMenuData(menu, menuData);
|
||||
|
||||
if( radioGroups != NULL ) {
|
||||
// Iterate radio groups
|
||||
JsonNode *radioGroup;
|
||||
json_foreach(radioGroup, radioGroups) {
|
||||
// Get item label
|
||||
processRadioGroupJSON(menu, radioGroup);
|
||||
}
|
||||
}
|
||||
|
||||
return menu->menu;
|
||||
}
|
||||
|
||||
97
v2/internal/ffenestri/menu_darwin_old.h
Normal file
97
v2/internal/ffenestri/menu_darwin_old.h
Normal file
@@ -0,0 +1,97 @@
|
||||
//
|
||||
// Created by Lea Anthony on 6/1/21.
|
||||
//
|
||||
|
||||
#ifndef MENU_DARWIN_H
|
||||
#define MENU_DARWIN_H
|
||||
|
||||
#include "common.h"
|
||||
#include "ffenestri_darwin.h"
|
||||
|
||||
enum MenuItemType {Text = 0, Checkbox = 1, Radio = 2};
|
||||
enum MenuType {ApplicationMenuType = 0, ContextMenuType = 1, TrayMenuType = 2};
|
||||
static const char *MenuTypeAsString[] = {
|
||||
"ApplicationMenu", "ContextMenu", "TrayMenu",
|
||||
};
|
||||
|
||||
extern void messageFromWindowCallback(const char *);
|
||||
|
||||
struct TrayMenuStore;
|
||||
|
||||
typedef struct {
|
||||
|
||||
const char *title;
|
||||
|
||||
/*** Internal ***/
|
||||
|
||||
struct hashmap_s menuItemMap;
|
||||
struct hashmap_s radioGroupMap;
|
||||
|
||||
// Vector to keep track of callback data memory
|
||||
vec_void_t callbackDataCache;
|
||||
|
||||
// The NSMenu for this menu
|
||||
id menu;
|
||||
|
||||
// A reference to the Menu store
|
||||
struct TrayMenuStore *store;
|
||||
|
||||
// The commands for the menu callbacks
|
||||
const char *callbackCommand;
|
||||
|
||||
// This indicates if we are an Application Menu, tray menu or context menu
|
||||
enum MenuType menuType;
|
||||
|
||||
|
||||
} Menu;
|
||||
|
||||
typedef struct {
|
||||
struct TrayMenuStore *store;
|
||||
Menu *menu;
|
||||
const char *menuItemID;
|
||||
enum MenuItemType menuItemType;
|
||||
} MenuItemCallbackData;
|
||||
|
||||
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
|
||||
Menu* NewMenu(JsonNode *menuData, JsonNode *radioGroups, struct TrayMenuStore *store);
|
||||
|
||||
Menu* NewApplicationMenu(const char *menuAsJSON, struct TrayMenuStore *store);
|
||||
MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, const char *menuItemID, enum MenuItemType menuItemType);
|
||||
|
||||
void DeleteMenu(Menu *menu);
|
||||
|
||||
// Creates a JSON message for the given menuItemID and data
|
||||
const char* createMenuClickedMessage(const char *menuItemID, const char *data);
|
||||
|
||||
// Callback for text menu items
|
||||
void menuItemCallback(id self, SEL cmd, id sender);
|
||||
id processAcceleratorKey(const char *key);
|
||||
|
||||
|
||||
void addSeparator(id menu);
|
||||
id createMenuItemNoAutorelease( id title, const char *action, const char *key);
|
||||
|
||||
id createMenuItem(id title, const char *action, const char *key);
|
||||
|
||||
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled);
|
||||
|
||||
id createMenu(id title);
|
||||
void createDefaultAppMenu(id parentMenu);
|
||||
void createDefaultEditMenu(id parentMenu);
|
||||
|
||||
void processMenuRole(Menu *menu, id parentMenu, JsonNode *item);
|
||||
// This converts a string array of modifiers into the
|
||||
// equivalent MacOS Modifier Flags
|
||||
unsigned long parseModifiers(const char **modifiers);
|
||||
id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *acceleratorkey, bool hasCallback);
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key, bool hasCallback);
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, bool hasCallback);
|
||||
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
||||
void processMenuData(Menu *menu, JsonNode *menuData);
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
|
||||
id ProcessMenu(Menu *menu, JsonNode *menuData, JsonNode *radioGroup);
|
||||
#endif //ASSETS_C_MENU_DARWIN_H
|
||||
101
v2/internal/ffenestri/menu_item.c
Normal file
101
v2/internal/ffenestri/menu_item.c
Normal file
@@ -0,0 +1,101 @@
|
||||
//
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#include "menu.h"
|
||||
|
||||
MenuItem* NewMenuItem(enum MenuItemType type, const char* ID, const char* label, const char* alternateLabel, bool disabled, bool hidden, bool checked, const char* colour, const char* font, int fontsize, const char* image, const char* acceleratorKey, const char** modifiers, bool hasCallback, Menu* submenu) {
|
||||
|
||||
MenuItem *result = NEW(MenuItem);
|
||||
|
||||
// Setup
|
||||
result->ID = STRCOPY(ID);
|
||||
result->label = STRCOPY(label);
|
||||
result->alternateLabel = STRCOPY(alternateLabel);
|
||||
result->disabled = disabled;
|
||||
result->hidden = hidden;
|
||||
result->colour = STRCOPY(colour);
|
||||
result->font = STRCOPY(font);
|
||||
result->fontSize = fontsize;
|
||||
result->image = STRCOPY(image);
|
||||
result->acceleratorKey = STRCOPY(acceleratorKey);
|
||||
result->modifiers = modifiers;
|
||||
result->hasCallback = hasCallback;
|
||||
result->type = type;
|
||||
result->checked = checked;
|
||||
result->submenu = submenu;
|
||||
|
||||
result->callbackData = NULL;
|
||||
|
||||
vec_init(&result->radioGroup);
|
||||
|
||||
SetupMenuItemPlatformData(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeleteMenuItem(MenuItem* menuItem) {
|
||||
|
||||
MEMFREE(menuItem->ID);
|
||||
MEMFREE(menuItem->label);
|
||||
MEMFREE(menuItem->alternateLabel);
|
||||
MEMFREE(menuItem->colour);
|
||||
MEMFREE(menuItem->font);
|
||||
MEMFREE(menuItem->image);
|
||||
MEMFREE(menuItem->acceleratorKey);
|
||||
|
||||
// Iterate the modifiers and free elements
|
||||
if( menuItem->modifiers != NULL ) {
|
||||
int i = 0;
|
||||
const char *nextItem = menuItem->modifiers[0];
|
||||
while (nextItem != NULL) {
|
||||
MEMFREE(nextItem);
|
||||
i++;
|
||||
nextItem = menuItem->modifiers[i];
|
||||
}
|
||||
MEMFREE(menuItem->modifiers);
|
||||
}
|
||||
|
||||
DeleteMenuItemCallbackData(menuItem->callbackData);
|
||||
|
||||
DeleteMenuItemPlatformData(menuItem);
|
||||
|
||||
MEMFREE(menuItem);
|
||||
}
|
||||
|
||||
JsonNode* MenuItemAsJSONObject(MenuItem* menuItem) {
|
||||
|
||||
JsonNode* result = json_mkobject();
|
||||
JSON_ADD_STRING(result, "ID", menuItem->ID);
|
||||
JSON_ADD_STRING(result, "label", menuItem->label);
|
||||
JSON_ADD_STRING(result, "alternateLabel", menuItem->alternateLabel);
|
||||
JSON_ADD_BOOL(result, "disabled", menuItem->disabled);
|
||||
JSON_ADD_BOOL(result, "hidden", menuItem->hidden);
|
||||
JSON_ADD_STRING(result, "colour", menuItem->colour);
|
||||
JSON_ADD_STRING(result, "font", menuItem->font);
|
||||
JSON_ADD_NUMBER(result, "fontsize", menuItem->fontSize);
|
||||
JSON_ADD_STRING(result, "image", menuItem->image);
|
||||
JSON_ADD_STRING(result, "acceleratorKey", menuItem->acceleratorKey);
|
||||
JSON_ADD_BOOL(result, "hasCallback", menuItem->hasCallback);
|
||||
JSON_ADD_STRING(result, "type", MenuItemTypeAsString[menuItem->type]);
|
||||
JSON_ADD_BOOL(result, "checked", menuItem->checked);
|
||||
return result;
|
||||
}
|
||||
|
||||
MenuItemCallbackData* NewMenuItemCallbackData(MenuManager *manager, const char* menuItemID, enum MenuItemType menuItemType) {
|
||||
MenuItemCallbackData* result = NEW(MenuItemCallbackData);
|
||||
|
||||
result->manager = manager;
|
||||
result->menuItemID = STRCOPY(menuItemID);
|
||||
result->menuItemType = menuItemType;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeleteMenuItemCallbackData(MenuItemCallbackData* callbackData) {
|
||||
|
||||
if( callbackData == NULL ) return;
|
||||
|
||||
MEMFREE(callbackData->menuItemID);
|
||||
MEMFREE(callbackData);
|
||||
}
|
||||
81
v2/internal/ffenestri/menu_manager.c
Normal file
81
v2/internal/ffenestri/menu_manager.c
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#include "menu.h"
|
||||
|
||||
MenuManager* NewMenuManager() {
|
||||
|
||||
MenuManager* result = malloc(sizeof(MenuManager));
|
||||
|
||||
// Allocate Hashmaps
|
||||
HASHMAP_INIT(result->menuItems, 32, "menuItems");
|
||||
HASHMAP_INIT(result->contextMenus, 4, "contextMenus");
|
||||
HASHMAP_INIT(result->trayMenus, 4, "trayMenus");
|
||||
|
||||
// Initialise other data
|
||||
result->applicationMenu = NULL;
|
||||
result->contextMenuData = NULL;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int deleteTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
DeleteTrayMenu(e->data);
|
||||
return -1; // Remove from hashmap
|
||||
}
|
||||
|
||||
int deleteMenuItem(void *const context, struct hashmap_element_s *const e) {
|
||||
DeleteMenuItem(e->data);
|
||||
return -1; // Remove from hashmap
|
||||
}
|
||||
|
||||
void DeleteMenuManager(MenuManager* manager) {
|
||||
|
||||
// Iterate hashmaps and delete items
|
||||
HASHMAP_ITERATE(manager->trayMenus, deleteTrayMenu, NULL);
|
||||
HASHMAP_ITERATE(manager->menuItems, deleteMenuItem, NULL);
|
||||
|
||||
// Delete applicationMenu
|
||||
DeleteMenu(manager->applicationMenu);
|
||||
|
||||
// Delete Hashmaps
|
||||
HASHMAP_DESTROY(manager->trayMenus);
|
||||
HASHMAP_DESTROY(manager->contextMenus);
|
||||
HASHMAP_DESTROY(manager->menuItems);
|
||||
|
||||
// Delete context menu data
|
||||
MEMFREE(manager->contextMenuData);
|
||||
}
|
||||
|
||||
TrayMenu* AddTrayMenuToManager(MenuManager* manager, const char* trayMenuJSON) {
|
||||
|
||||
// Parse JSON
|
||||
struct JsonNode* parsedJSON = mustParseJSON(trayMenuJSON);
|
||||
|
||||
// Get the ID
|
||||
const char *ID = mustJSONString(parsedJSON, "I");
|
||||
|
||||
// Check if there is already an entry for this menu
|
||||
TrayMenu* existingTrayMenu = HASHMAP_GET(manager->trayMenus, ID);
|
||||
if ( existingTrayMenu != NULL ) {
|
||||
json_delete(parsedJSON);
|
||||
return existingTrayMenu;
|
||||
}
|
||||
|
||||
// Create new menu
|
||||
TrayMenu* newMenu = NewTrayMenu(parsedJSON, manager);
|
||||
HASHMAP_PUT(manager->trayMenus, newMenu->ID, newMenu);
|
||||
|
||||
return newMenu;
|
||||
}
|
||||
|
||||
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
ShowTrayMenu(e->data);
|
||||
// 0 to retain element, -1 to delete.
|
||||
return 0;
|
||||
}
|
||||
void ShowTrayMenus(MenuManager* manager) {
|
||||
HASHMAP_ITERATE(manager->trayMenus, showTrayMenu, NULL);
|
||||
}
|
||||
|
||||
69
v2/internal/ffenestri/menu_tray.c
Normal file
69
v2/internal/ffenestri/menu_tray.c
Normal file
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// Created by Lea Anthony on 18/1/21.
|
||||
//
|
||||
|
||||
#include "menu.h"
|
||||
|
||||
TrayMenu* NewTrayMenu(JsonNode* parsedJSON, MenuManager *manager) {
|
||||
|
||||
// NULL GUARD
|
||||
if(parsedJSON == NULL) ABORT("[NewTrayMenu] parsedJSON == NULL");
|
||||
|
||||
// Create new tray menu
|
||||
TrayMenu* result = NEW(TrayMenu);
|
||||
|
||||
// Initialise other data
|
||||
result->ID = STRCOPY(mustJSONString(parsedJSON, "I"));
|
||||
result->Label = STRCOPY(getJSONString(parsedJSON, "l"));
|
||||
result->Icon = STRCOPY(getJSONString(parsedJSON, "i"));
|
||||
|
||||
// Process menu
|
||||
struct JsonNode* menuJSON = getJSONObject(parsedJSON, "m");
|
||||
struct JsonNode* radioJSON = getJSONObject(parsedJSON, "r");
|
||||
result->Menu = NewMenu(menuJSON, radioJSON, manager);
|
||||
|
||||
// Setup platform data
|
||||
SetupTrayMenuPlatformData(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeleteTrayMenu(TrayMenu *trayMenu) {
|
||||
|
||||
// NULL guard
|
||||
if( trayMenu == NULL ) return;
|
||||
|
||||
// Free the strings
|
||||
MEMFREE(trayMenu->ID);
|
||||
MEMFREE(trayMenu->Label);
|
||||
MEMFREE(trayMenu->Icon);
|
||||
|
||||
// Delete the menu
|
||||
DeleteMenu(trayMenu->Menu);
|
||||
|
||||
// Delete the platform data
|
||||
DeleteTrayMenuPlatformData(trayMenu);
|
||||
|
||||
// Free tray menu
|
||||
MEMFREE(trayMenu);
|
||||
}
|
||||
|
||||
const char* TrayMenuAsJSON(TrayMenu* menu) {
|
||||
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
JSON_ADD_STRING(jsonObject, "ID", menu->ID);
|
||||
JSON_ADD_STRING(jsonObject, "Label", menu->Label);
|
||||
JSON_ADD_STRING(jsonObject, "Icon", menu->Icon);
|
||||
JSON_ADD_OBJECT(jsonObject, "Menu", MenuAsJSONObject(menu->Menu));
|
||||
return json_encode(jsonObject);
|
||||
}
|
||||
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->Icon == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformUpdateTrayIcon(trayMenu);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
25
v2/internal/ffenestri/test_app/CMakeLists.txt
Normal file
25
v2/internal/ffenestri/test_app/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
cmake_minimum_required(VERSION 3.17)
|
||||
project(test_app)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -g")
|
||||
set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -fsanitize=leak -g")
|
||||
|
||||
add_executable(test_app test.c)
|
||||
add_library(menus STATIC ../menu_manager.c ../menu.c ../menu_tray.c ../menu_item.c )
|
||||
add_library(common STATIC ../common.c ../utf8.h)
|
||||
add_library(json STATIC ../json.c)
|
||||
add_library(vec STATIC ../vec.c)
|
||||
|
||||
if( CMAKE_HOST_APPLE )
|
||||
find_library(WEBKIT WebKit)
|
||||
add_library(runtime STATIC ../runtime_darwin.c)
|
||||
add_library(ffenestri STATIC ../ffenestri_darwin.c)
|
||||
add_library(defaulticons STATIC ../defaultdialogicons_darwin.c)
|
||||
add_library(platform STATIC ../menu_darwin.c)
|
||||
target_link_libraries(test_app objc ${WEBKIT} ffenestri platform runtime)
|
||||
endif()
|
||||
|
||||
target_link_libraries(test_app vec json common menus)
|
||||
include_directories(..)
|
||||
include_directories(.)
|
||||
9
v2/internal/ffenestri/test_app/assets.h
Normal file
9
v2/internal/ffenestri/test_app/assets.h
Normal file
File diff suppressed because one or more lines are too long
81
v2/internal/ffenestri/test_app/test.c
Normal file
81
v2/internal/ffenestri/test_app/test.c
Normal file
File diff suppressed because one or more lines are too long
21
v2/internal/ffenestri/test_menumanager/CMakeLists.txt
Normal file
21
v2/internal/ffenestri/test_menumanager/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
cmake_minimum_required(VERSION 3.17)
|
||||
project(test_menumanager)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -g")
|
||||
set (CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -fsanitize=leak -g")
|
||||
|
||||
add_executable(test_menumanager test.c minunit.h)
|
||||
add_library(menus STATIC ../menu_manager.c ../menu.c ../menu_tray.c ../menu_item.c)
|
||||
add_library(common STATIC ../common.c ../utf8.h)
|
||||
add_library(json STATIC ../json.c)
|
||||
add_library(vec STATIC ../vec.c)
|
||||
|
||||
if( CMAKE_HOST_APPLE )
|
||||
add_library(platform STATIC ../menu_darwin.c)
|
||||
target_link_libraries(test_menumanager objc)
|
||||
endif()
|
||||
|
||||
target_link_libraries(test_menumanager vec json common menus platform)
|
||||
include_directories(..)
|
||||
include_directories(.)
|
||||
20
v2/internal/ffenestri/test_menumanager/minunit.LICENSE.txt
Normal file
20
v2/internal/ffenestri/test_menumanager/minunit.LICENSE.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2012 David Siñuela Pastor, siu.4coders@gmail.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
391
v2/internal/ffenestri/test_menumanager/minunit.h
Normal file
391
v2/internal/ffenestri/test_menumanager/minunit.h
Normal file
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright (c) 2012 David Siñuela Pastor, siu.4coders@gmail.com
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef MINUNIT_MINUNIT_H
|
||||
#define MINUNIT_MINUNIT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <Windows.h>
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#define __func__ __FUNCTION__
|
||||
#endif
|
||||
|
||||
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
|
||||
|
||||
/* Change POSIX C SOURCE version for pure c99 compilers */
|
||||
#if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L
|
||||
#undef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#endif
|
||||
|
||||
#include <unistd.h> /* POSIX flags */
|
||||
#include <time.h> /* clock_gettime(), time() */
|
||||
#include <sys/time.h> /* gethrtime(), gettimeofday() */
|
||||
#include <sys/resource.h>
|
||||
#include <sys/times.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__MACH__) && defined(__APPLE__)
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_time.h>
|
||||
#endif
|
||||
|
||||
#if __GNUC__ >= 5 && !defined(__STDC_VERSION__)
|
||||
#define __func__ __extension__ __FUNCTION__
|
||||
#endif
|
||||
|
||||
#else
|
||||
#error "Unable to define timers for an unknown OS."
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
/* Maximum length of last message */
|
||||
#define MINUNIT_MESSAGE_LEN 1024
|
||||
/* Accuracy with which floats are compared */
|
||||
#define MINUNIT_EPSILON 1E-12
|
||||
|
||||
/* Misc. counters */
|
||||
static int minunit_run = 0;
|
||||
static int minunit_assert = 0;
|
||||
static int minunit_fail = 0;
|
||||
static int minunit_status = 0;
|
||||
|
||||
/* Timers */
|
||||
static double minunit_real_timer = 0;
|
||||
static double minunit_proc_timer = 0;
|
||||
|
||||
/* Last message */
|
||||
static char minunit_last_message[MINUNIT_MESSAGE_LEN];
|
||||
|
||||
/* Test setup and teardown function pointers */
|
||||
static void (*minunit_setup)(void) = NULL;
|
||||
static void (*minunit_teardown)(void) = NULL;
|
||||
|
||||
/* Definitions */
|
||||
#define MU_TEST(method_name) static void method_name(void)
|
||||
#define MU_TEST_SUITE(suite_name) static void suite_name(void)
|
||||
|
||||
#define MU__SAFE_BLOCK(block) do {\
|
||||
block\
|
||||
} while(0)
|
||||
|
||||
/* Run test suite and unset setup and teardown functions */
|
||||
#define MU_RUN_SUITE(suite_name) MU__SAFE_BLOCK(\
|
||||
suite_name();\
|
||||
minunit_setup = NULL;\
|
||||
minunit_teardown = NULL;\
|
||||
)
|
||||
|
||||
/* Configure setup and teardown functions */
|
||||
#define MU_SUITE_CONFIGURE(setup_fun, teardown_fun) MU__SAFE_BLOCK(\
|
||||
minunit_setup = setup_fun;\
|
||||
minunit_teardown = teardown_fun;\
|
||||
)
|
||||
|
||||
/* Test runner */
|
||||
#define MU_RUN_TEST(test) MU__SAFE_BLOCK(\
|
||||
if (minunit_real_timer==0 && minunit_proc_timer==0) {\
|
||||
minunit_real_timer = mu_timer_real();\
|
||||
minunit_proc_timer = mu_timer_cpu();\
|
||||
}\
|
||||
if (minunit_setup) (*minunit_setup)();\
|
||||
minunit_status = 0;\
|
||||
test();\
|
||||
minunit_run++;\
|
||||
if (minunit_status) {\
|
||||
minunit_fail++;\
|
||||
printf("F");\
|
||||
printf("\n%s\n", minunit_last_message);\
|
||||
}\
|
||||
fflush(stdout);\
|
||||
if (minunit_teardown) (*minunit_teardown)();\
|
||||
)
|
||||
|
||||
/* Report */
|
||||
#define MU_REPORT() MU__SAFE_BLOCK(\
|
||||
double minunit_end_real_timer;\
|
||||
double minunit_end_proc_timer;\
|
||||
printf("\n\n%d tests, %d assertions, %d failures\n", minunit_run, minunit_assert, minunit_fail);\
|
||||
minunit_end_real_timer = mu_timer_real();\
|
||||
minunit_end_proc_timer = mu_timer_cpu();\
|
||||
printf("\nFinished in %.8f seconds (real) %.8f seconds (proc)\n\n",\
|
||||
minunit_end_real_timer - minunit_real_timer,\
|
||||
minunit_end_proc_timer - minunit_proc_timer);\
|
||||
)
|
||||
#define MU_EXIT_CODE minunit_fail
|
||||
|
||||
/* Assertions */
|
||||
#define mu_check(test) MU__SAFE_BLOCK(\
|
||||
minunit_assert++;\
|
||||
if (!(test)) {\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, #test);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
} else {\
|
||||
printf(".");\
|
||||
}\
|
||||
)
|
||||
|
||||
#define mu_fail(message) MU__SAFE_BLOCK(\
|
||||
minunit_assert++;\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
)
|
||||
|
||||
#define mu_assert(test, message) MU__SAFE_BLOCK(\
|
||||
minunit_assert++;\
|
||||
if (!(test)) {\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
} else {\
|
||||
printf(".");\
|
||||
}\
|
||||
)
|
||||
|
||||
#define mu_assert_int_eq(expected, result) MU__SAFE_BLOCK(\
|
||||
int minunit_tmp_e;\
|
||||
int minunit_tmp_r;\
|
||||
minunit_assert++;\
|
||||
minunit_tmp_e = (expected);\
|
||||
minunit_tmp_r = (result);\
|
||||
if (minunit_tmp_e != minunit_tmp_r) {\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %d expected but was %d", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
} else {\
|
||||
printf(".");\
|
||||
}\
|
||||
)
|
||||
|
||||
#define mu_assert_double_eq(expected, result) MU__SAFE_BLOCK(\
|
||||
double minunit_tmp_e;\
|
||||
double minunit_tmp_r;\
|
||||
minunit_assert++;\
|
||||
minunit_tmp_e = (expected);\
|
||||
minunit_tmp_r = (result);\
|
||||
if (fabs(minunit_tmp_e-minunit_tmp_r) > MINUNIT_EPSILON) {\
|
||||
int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON);\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %.*g expected but was %.*g", __func__, __FILE__, __LINE__, minunit_significant_figures, minunit_tmp_e, minunit_significant_figures, minunit_tmp_r);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
} else {\
|
||||
printf(".");\
|
||||
}\
|
||||
)
|
||||
|
||||
#define mu_assert_string_eq(expected, result) MU__SAFE_BLOCK(\
|
||||
const char* minunit_tmp_e = expected;\
|
||||
const char* minunit_tmp_r = result;\
|
||||
minunit_assert++;\
|
||||
if (!minunit_tmp_e) {\
|
||||
minunit_tmp_e = "<null pointer>";\
|
||||
}\
|
||||
if (!minunit_tmp_r) {\
|
||||
minunit_tmp_r = "<null pointer>";\
|
||||
}\
|
||||
if(strcmp(minunit_tmp_e, minunit_tmp_r) != 0) {\
|
||||
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: '%s' expected but was '%s'", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
|
||||
minunit_status = 1;\
|
||||
return;\
|
||||
} else {\
|
||||
printf(".");\
|
||||
}\
|
||||
)
|
||||
|
||||
/*
|
||||
* The following two functions were written by David Robert Nadeau
|
||||
* from http://NadeauSoftware.com/ and distributed under the
|
||||
* Creative Commons Attribution 3.0 Unported License
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the real time, in seconds, or -1.0 if an error occurred.
|
||||
*
|
||||
* Time is measured since an arbitrary and OS-dependent start time.
|
||||
* The returned real time is only useful for computing an elapsed time
|
||||
* between two calls to this function.
|
||||
*/
|
||||
static double mu_timer_real(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
/* Windows 2000 and later. ---------------------------------- */
|
||||
LARGE_INTEGER Time;
|
||||
LARGE_INTEGER Frequency;
|
||||
|
||||
QueryPerformanceFrequency(&Frequency);
|
||||
QueryPerformanceCounter(&Time);
|
||||
|
||||
Time.QuadPart *= 1000000;
|
||||
Time.QuadPart /= Frequency.QuadPart;
|
||||
|
||||
return (double)Time.QuadPart / 1000000.0;
|
||||
|
||||
#elif (defined(__hpux) || defined(hpux)) || ((defined(__sun__) || defined(__sun) || defined(sun)) && (defined(__SVR4) || defined(__svr4__)))
|
||||
/* HP-UX, Solaris. ------------------------------------------ */
|
||||
return (double)gethrtime( ) / 1000000000.0;
|
||||
|
||||
#elif defined(__MACH__) && defined(__APPLE__)
|
||||
/* OSX. ----------------------------------------------------- */
|
||||
static double timeConvert = 0.0;
|
||||
if ( timeConvert == 0.0 )
|
||||
{
|
||||
mach_timebase_info_data_t timeBase;
|
||||
(void)mach_timebase_info( &timeBase );
|
||||
timeConvert = (double)timeBase.numer /
|
||||
(double)timeBase.denom /
|
||||
1000000000.0;
|
||||
}
|
||||
return (double)mach_absolute_time( ) * timeConvert;
|
||||
|
||||
#elif defined(_POSIX_VERSION)
|
||||
/* POSIX. --------------------------------------------------- */
|
||||
struct timeval tm;
|
||||
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
|
||||
{
|
||||
struct timespec ts;
|
||||
#if defined(CLOCK_MONOTONIC_PRECISE)
|
||||
/* BSD. --------------------------------------------- */
|
||||
const clockid_t id = CLOCK_MONOTONIC_PRECISE;
|
||||
#elif defined(CLOCK_MONOTONIC_RAW)
|
||||
/* Linux. ------------------------------------------- */
|
||||
const clockid_t id = CLOCK_MONOTONIC_RAW;
|
||||
#elif defined(CLOCK_HIGHRES)
|
||||
/* Solaris. ----------------------------------------- */
|
||||
const clockid_t id = CLOCK_HIGHRES;
|
||||
#elif defined(CLOCK_MONOTONIC)
|
||||
/* AIX, BSD, Linux, POSIX, Solaris. ----------------- */
|
||||
const clockid_t id = CLOCK_MONOTONIC;
|
||||
#elif defined(CLOCK_REALTIME)
|
||||
/* AIX, BSD, HP-UX, Linux, POSIX. ------------------- */
|
||||
const clockid_t id = CLOCK_REALTIME;
|
||||
#else
|
||||
const clockid_t id = (clockid_t)-1; /* Unknown. */
|
||||
#endif /* CLOCK_* */
|
||||
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
|
||||
return (double)ts.tv_sec +
|
||||
(double)ts.tv_nsec / 1000000000.0;
|
||||
/* Fall thru. */
|
||||
}
|
||||
#endif /* _POSIX_TIMERS */
|
||||
|
||||
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, POSIX, Solaris. ----- */
|
||||
gettimeofday( &tm, NULL );
|
||||
return (double)tm.tv_sec + (double)tm.tv_usec / 1000000.0;
|
||||
#else
|
||||
return -1.0; /* Failed. */
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of CPU time used by the current process,
|
||||
* in seconds, or -1.0 if an error occurred.
|
||||
*/
|
||||
static double mu_timer_cpu(void)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
/* Windows -------------------------------------------------- */
|
||||
FILETIME createTime;
|
||||
FILETIME exitTime;
|
||||
FILETIME kernelTime;
|
||||
FILETIME userTime;
|
||||
|
||||
/* This approach has a resolution of 1/64 second. Unfortunately, Windows' API does not offer better */
|
||||
if ( GetProcessTimes( GetCurrentProcess( ),
|
||||
&createTime, &exitTime, &kernelTime, &userTime ) != 0 )
|
||||
{
|
||||
ULARGE_INTEGER userSystemTime;
|
||||
memcpy(&userSystemTime, &userTime, sizeof(ULARGE_INTEGER));
|
||||
return (double)userSystemTime.QuadPart / 10000000.0;
|
||||
}
|
||||
|
||||
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
|
||||
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris --------- */
|
||||
|
||||
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
|
||||
/* Prefer high-res POSIX timers, when available. */
|
||||
{
|
||||
clockid_t id;
|
||||
struct timespec ts;
|
||||
#if _POSIX_CPUTIME > 0
|
||||
/* Clock ids vary by OS. Query the id, if possible. */
|
||||
if ( clock_getcpuclockid( 0, &id ) == -1 )
|
||||
#endif
|
||||
#if defined(CLOCK_PROCESS_CPUTIME_ID)
|
||||
/* Use known clock id for AIX, Linux, or Solaris. */
|
||||
id = CLOCK_PROCESS_CPUTIME_ID;
|
||||
#elif defined(CLOCK_VIRTUAL)
|
||||
/* Use known clock id for BSD or HP-UX. */
|
||||
id = CLOCK_VIRTUAL;
|
||||
#else
|
||||
id = (clockid_t)-1;
|
||||
#endif
|
||||
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
|
||||
return (double)ts.tv_sec +
|
||||
(double)ts.tv_nsec / 1000000000.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(RUSAGE_SELF)
|
||||
{
|
||||
struct rusage rusage;
|
||||
if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
|
||||
return (double)rusage.ru_utime.tv_sec +
|
||||
(double)rusage.ru_utime.tv_usec / 1000000.0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_SC_CLK_TCK)
|
||||
{
|
||||
const double ticks = (double)sysconf( _SC_CLK_TCK );
|
||||
struct tms tms;
|
||||
if ( times( &tms ) != (clock_t)-1 )
|
||||
return (double)tms.tms_utime / ticks;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CLOCKS_PER_SEC)
|
||||
{
|
||||
clock_t cl = clock( );
|
||||
if ( cl != (clock_t)-1 )
|
||||
return (double)cl / (double)CLOCKS_PER_SEC;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
return -1; /* Failed. */
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MINUNIT_MINUNIT_H */
|
||||
71
v2/internal/ffenestri/test_menumanager/test.c
Normal file
71
v2/internal/ffenestri/test_menumanager/test.c
Normal file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Created by Lea Anthony on 12/1/21.
|
||||
//
|
||||
|
||||
#include "minunit.h"
|
||||
#include "menu.h"
|
||||
|
||||
#define empty "{\"I\":\"T1\"}"
|
||||
#define emptyExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":null}"
|
||||
#define labelOnly "{\"I\":\"T1\",\"l\":\"test\"}"
|
||||
#define labelOnlyExpected "{\"ID\":\"T1\",\"Label\":\"test\",\"Icon\":null,\"Menu\":null}"
|
||||
#define iconOnly "{\"I\":\"T1\",\"i\":\"svelte\"}"
|
||||
#define iconOnlyExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":\"svelte\",\"Menu\":null}"
|
||||
#define iconLabel "{\"I\":\"T1\",\"l\":\"test\",\"i\":\"svelte\"}"
|
||||
#define iconLabelExpected "{\"ID\":\"T1\",\"Label\":\"test\",\"Icon\":\"svelte\",\"Menu\":null}"
|
||||
#define blankLabel "{\"I\":\"T1\"}"
|
||||
#define blankLabelExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":null}"
|
||||
#define blankMenu "{\"I\":\"T1\"}"
|
||||
#define blankMenuExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":null}"
|
||||
#define menuTextItem "{\"I\":\"T1\",\"l\":\"test\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\"}]}"
|
||||
#define menuTextItemExpected "{\"ID\":\"T1\",\"Label\":\"test\",\"Icon\":null,\"Menu\":{\"Label\":\"\",\"Items\":[{\"ID\":\"1\",\"label\":\"test\",\"alternateLabel\":null,\"disabled\":false,\"hidden\":false,\"colour\":null,\"font\":null,\"fontsize\":0,\"image\":null,\"acceleratorKey\":null,\"hasCallback\":false,\"type\":\"Text\",\"checked\":false}]}}"
|
||||
#define checkboxItem "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"c\",\"c\":true}]}"
|
||||
#define checkboxItemExpected "{\"ID\":\"T1\",\"Label\":null,\"Icon\":null,\"Menu\":{\"Label\":\"\",\"Items\":[{\"ID\":\"1\",\"label\":\"test\",\"alternateLabel\":null,\"disabled\":false,\"hidden\":false,\"colour\":null,\"font\":null,\"fontsize\":0,\"image\":null,\"acceleratorKey\":null,\"hasCallback\":false,\"type\":\"Checkbox\",\"checked\":true}]}}"
|
||||
#define radioGroupItems "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"option 1\",\"t\":\"r\",\"c\":true},{\"I\":\"2\",\"l\":\"option 2\",\"t\":\"r\"},{\"I\":\"3\",\"l\":\"option 3\",\"t\":\"r\"}],\"r\":[{\"Members\":[\"1\",\"2\",\"3\"],\"Length\":3}]}"
|
||||
#define radioGroupItemsExpected ""
|
||||
#define callbackItem "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"Preferences\",\"t\":\"t\",\"C\":true}]}"
|
||||
#define callbackItemExpected ""
|
||||
|
||||
const char* tests[] = {
|
||||
empty, emptyExpected,
|
||||
labelOnly, labelOnlyExpected,
|
||||
iconOnly, iconOnlyExpected,
|
||||
iconLabel, iconLabelExpected,
|
||||
blankLabel, blankLabelExpected,
|
||||
blankMenu, blankMenuExpected,
|
||||
menuTextItem, menuTextItemExpected,
|
||||
checkboxItem, checkboxItemExpected,
|
||||
radioGroupItems, radioGroupItemsExpected,
|
||||
callbackItem, callbackItemExpected,
|
||||
};
|
||||
|
||||
MU_TEST(manager_creation) {
|
||||
MenuManager* manager = NewMenuManager();
|
||||
mu_assert(manager->applicationMenu == NULL, "app menu");
|
||||
mu_assert(manager->contextMenuData == NULL, "context menu data");
|
||||
mu_assert_int_eq(hashmap_num_entries(&manager->contextMenus), 0);
|
||||
mu_assert_int_eq(hashmap_num_entries(&manager->trayMenus), 0);
|
||||
mu_assert_int_eq(hashmap_num_entries(&manager->menuItems), 0);
|
||||
DeleteMenuManager(manager);
|
||||
}
|
||||
|
||||
MU_TEST(add_tray) {
|
||||
for( int count = 0; count < sizeof(tests) / sizeof(tests[0]); count += 2 ) {
|
||||
MenuManager* manager = NewMenuManager();
|
||||
TrayMenu* tray = AddTrayMenu(manager, tests[count]);
|
||||
const char* trayJSON = TrayMenuAsJSON(tray);
|
||||
mu_assert_string_eq(tests[count+1], trayJSON);
|
||||
DeleteMenuManager(manager);
|
||||
}
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_suite) {
|
||||
MU_RUN_TEST(manager_creation);
|
||||
MU_RUN_TEST(add_tray);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
MU_RUN_SUITE(test_suite);
|
||||
MU_REPORT();
|
||||
return MU_EXIT_CODE;
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
// Global because it's a singleton
|
||||
struct hashmap_s trayIconCache;
|
||||
|
||||
TrayMenu* NewTrayMenu(const char* menuJSON) {
|
||||
TrayMenu* NewTrayMenu(const char* menuJSON, struct TrayMenuStore* store) {
|
||||
TrayMenu* result = malloc(sizeof(TrayMenu));
|
||||
|
||||
/*
|
||||
@@ -21,26 +21,29 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
|
||||
ABORT("[NewTrayMenu] Unable to parse TrayMenu JSON: %s", menuJSON);
|
||||
}
|
||||
|
||||
// Save reference to this json
|
||||
result->processedJSON = processedJSON;
|
||||
|
||||
// TODO: Make this configurable
|
||||
result->trayIconPosition = NSImageLeft;
|
||||
|
||||
result->ID = mustJSONString(processedJSON, "ID");
|
||||
result->label = mustJSONString(processedJSON, "Label");
|
||||
result->icon = mustJSONString(processedJSON, "Icon");
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
result->ID = STRCOPY(mustJSONString(processedJSON, "I"));
|
||||
result->label = STRCOPY(getJSONString(processedJSON, "l"));
|
||||
result->icon = STRCOPY(getJSONString(processedJSON, "i"));
|
||||
result->menu = NULL;
|
||||
|
||||
// Create the menu
|
||||
result->menu = NewMenu(processedMenu);
|
||||
JsonNode* menu = getJSONObject(processedJSON, "m");
|
||||
if( menu != NULL ) {
|
||||
|
||||
JsonNode* radioGroups = getJSONObject(processedJSON, "r");
|
||||
|
||||
// Create the menu
|
||||
result->menu = NewMenu(menu, radioGroups, store);
|
||||
result->menu->menuType = TrayMenuType;
|
||||
}
|
||||
|
||||
// Init tray status bar item
|
||||
result->statusbaritem = NULL;
|
||||
|
||||
// Set the menu type and store the tray ID in the parent data
|
||||
result->menu->menuType = TrayMenuType;
|
||||
result->menu->parentData = (void*) result->ID;
|
||||
// Free JSON
|
||||
json_delete(processedJSON);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -50,17 +53,6 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
|
||||
}
|
||||
|
||||
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->label == NULL ) {
|
||||
return;
|
||||
}
|
||||
// Update button label
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
msg(statusBarButton, s("setTitle:"), str(label));
|
||||
}
|
||||
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
|
||||
// Exit early if NULL
|
||||
@@ -104,8 +96,9 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
UpdateTrayLabel(trayMenu, trayMenu->label);
|
||||
|
||||
// Update the menu
|
||||
id menu = GetMenu(trayMenu->menu);
|
||||
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
||||
if (trayMenu->menu != NULL ) {
|
||||
msg(trayMenu->statusbaritem, s("setMenu:"), trayMenu->menu->menu);
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
|
||||
@@ -118,17 +111,11 @@ void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
|
||||
// Set the new one
|
||||
currentMenu->menu = newMenu->menu;
|
||||
|
||||
// Delete the old JSON
|
||||
json_delete(currentMenu->processedJSON);
|
||||
|
||||
// Set the new JSON
|
||||
currentMenu->processedJSON = newMenu->processedJSON;
|
||||
|
||||
// Copy the other data
|
||||
currentMenu->ID = newMenu->ID;
|
||||
currentMenu->label = newMenu->label;
|
||||
currentMenu->ID = STRCOPY(newMenu->ID);
|
||||
currentMenu->label = STRCOPY(newMenu->label);
|
||||
currentMenu->trayIconPosition = newMenu->trayIconPosition;
|
||||
currentMenu->icon = newMenu->icon;
|
||||
currentMenu->icon = STRCOPY(newMenu->icon);
|
||||
|
||||
}
|
||||
|
||||
@@ -140,10 +127,10 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
|
||||
// Delete the menu
|
||||
DeleteMenu(trayMenu->menu);
|
||||
|
||||
// Free JSON
|
||||
if (trayMenu->processedJSON != NULL ) {
|
||||
json_delete(trayMenu->processedJSON);
|
||||
}
|
||||
// Free strings
|
||||
MEMFREE(trayMenu->label);
|
||||
MEMFREE(trayMenu->icon);
|
||||
MEMFREE(trayMenu->ID);
|
||||
|
||||
// Free the status item
|
||||
if ( trayMenu->statusbaritem != NULL ) {
|
||||
@@ -189,14 +176,3 @@ void LoadTrayIcons() {
|
||||
}
|
||||
}
|
||||
|
||||
void UnloadTrayIcons() {
|
||||
// Release the tray cache images
|
||||
if( hashmap_num_entries(&trayIconCache) > 0 ) {
|
||||
if (0!=hashmap_iterate_pairs(&trayIconCache, releaseNSObject, NULL)) {
|
||||
ABORT("failed to release hashmap entries!");
|
||||
}
|
||||
}
|
||||
|
||||
//Free radio groups hashmap
|
||||
hashmap_destroy(&trayIconCache);
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
#define TRAYMENU_DARWIN_H
|
||||
|
||||
#include "common.h"
|
||||
#include "menu_darwin.h"
|
||||
#include "menu_darwin_old.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -18,12 +18,9 @@ typedef struct {
|
||||
|
||||
id statusbaritem;
|
||||
int trayIconPosition;
|
||||
|
||||
JsonNode* processedJSON;
|
||||
|
||||
} TrayMenu;
|
||||
|
||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
||||
TrayMenu* NewTrayMenu(const char *trayJSON, struct TrayMenuStore* store);
|
||||
void DumpTrayMenu(TrayMenu* trayMenu);
|
||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
||||
|
||||
@@ -16,6 +16,11 @@ TrayMenuStore* NewTrayMenuStore() {
|
||||
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
|
||||
}
|
||||
|
||||
// Allocate menu item store
|
||||
if( 0 != hashmap_create((const unsigned)8, &result->menuItemMap)) {
|
||||
ABORT("[NewTrayMenuStore] Not enough memory to allocate menuItemMap!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -29,7 +34,7 @@ void DumpTrayMenuStore(TrayMenuStore* store) {
|
||||
}
|
||||
|
||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON, (struct TrayMenuStore *) store);
|
||||
|
||||
//TODO: check if there is already an entry for this menu
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
@@ -65,6 +70,9 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
|
||||
|
||||
// Destroy tray menu map
|
||||
hashmap_destroy(&store->trayMenuMap);
|
||||
|
||||
// Destroy menu item map
|
||||
hashmap_destroy(&store->menuItemMap);
|
||||
}
|
||||
|
||||
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||
@@ -81,6 +89,15 @@ TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||
return result;
|
||||
}
|
||||
|
||||
id GetMenuItemFromStore(TrayMenuStore* store, const char* menuItemID) {
|
||||
return hashmap_get(&store->menuItemMap, menuItemID, strlen(menuItemID));
|
||||
}
|
||||
|
||||
void SaveMenuItemInStore(TrayMenuStore* store, const char* menuItemID, id nsmenuitem) {
|
||||
hashmap_put(&store->menuItemMap, menuItemID, strlen(menuItemID), nsmenuitem);
|
||||
}
|
||||
|
||||
|
||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||
// Parse the JSON
|
||||
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
||||
@@ -95,9 +112,8 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||
|
||||
}
|
||||
|
||||
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
||||
// DumpTrayMenu(newMenu);
|
||||
TrayMenu* UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON, (struct TrayMenuStore *) store);
|
||||
|
||||
// Get the current menu
|
||||
TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
|
||||
@@ -107,11 +123,8 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
// Store the new menu
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
|
||||
// Show it
|
||||
ShowTrayMenu(newMenu);
|
||||
return;
|
||||
return newMenu;
|
||||
}
|
||||
// DumpTrayMenu(currentMenu);
|
||||
|
||||
// Save the status bar reference
|
||||
newMenu->statusbaritem = currentMenu->statusbaritem;
|
||||
@@ -127,7 +140,11 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
|
||||
// Show the updated menu
|
||||
ShowTrayMenu(newMenu);
|
||||
return newMenu;
|
||||
|
||||
}
|
||||
|
||||
const char* GetContextMenuDataFromStore(TrayMenuStore *store) {
|
||||
return store->contextMenuData;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#ifndef TRAYMENUSTORE_DARWIN_H
|
||||
#define TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
#include "traymenu_darwin.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
int dummy;
|
||||
@@ -13,15 +15,26 @@ typedef struct {
|
||||
// It maps tray IDs to TrayMenu*
|
||||
struct hashmap_s trayMenuMap;
|
||||
|
||||
// This is our menu item map
|
||||
// It maps menu Item IDs to NSMenuItems
|
||||
struct hashmap_s menuItemMap;
|
||||
|
||||
const char* contextMenuData;
|
||||
|
||||
} TrayMenuStore;
|
||||
|
||||
TrayMenuStore* NewTrayMenuStore();
|
||||
|
||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON);
|
||||
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
|
||||
TrayMenu* UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
|
||||
void ShowTrayMenusInStore(TrayMenuStore* store);
|
||||
void DeleteTrayMenuStore(TrayMenuStore* store);
|
||||
|
||||
void SaveMenuItemInStore(TrayMenuStore* store, const char* menuItemID, id nsmenuitem);
|
||||
|
||||
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID);
|
||||
id GetMenuItemFromStore(TrayMenuStore* store, const char* menuItemID);
|
||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
||||
const char* GetContextMenuDataFromStore(TrayMenuStore *store);
|
||||
|
||||
#endif //TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
1292
v2/internal/ffenestri/utf8.h
Normal file
1292
v2/internal/ffenestri/utf8.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,8 @@
|
||||
( vec_splice_(vec_unpack_(v), start, count),\
|
||||
(v)->length -= (count) )
|
||||
|
||||
#define vec_size(v) \
|
||||
(v)->length
|
||||
|
||||
#define vec_swapsplice(v, start, count)\
|
||||
( vec_swapsplice_(vec_unpack_(v), start, count),\
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetVersionTags gets the list of tags on the Wails repo
|
||||
// It returns a list of sorted tags in descending order
|
||||
func GetVersionTags() ([]*SemanticVersion, error) {
|
||||
|
||||
result := []*SemanticVersion{}
|
||||
var err error
|
||||
|
||||
resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/tags")
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
data := []map[string]interface{}{}
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Convert tag data to Version structs
|
||||
for _, tag := range data {
|
||||
version := tag["name"].(string)
|
||||
if !strings.HasPrefix(version, "v2") {
|
||||
continue
|
||||
}
|
||||
semver, err := NewSemanticVersion(version)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = append(result, semver)
|
||||
}
|
||||
|
||||
// Reverse Sort
|
||||
sort.Sort(sort.Reverse(SemverCollection(result)))
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GetLatestStableRelease gets the latest stable release on GitHub
|
||||
func GetLatestStableRelease() (result *SemanticVersion, err error) {
|
||||
|
||||
tags, err := GetVersionTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if tag.IsRelease() {
|
||||
return tag, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no release tag found")
|
||||
}
|
||||
|
||||
// GetLatestPreRelease gets the latest prerelease on GitHub
|
||||
func GetLatestPreRelease() (result *SemanticVersion, err error) {
|
||||
|
||||
tags, err := GetVersionTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if tag.IsPreRelease() {
|
||||
return tag, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no prerelease tag found")
|
||||
}
|
||||
|
||||
// IsValidTag returns true if the given string is a valid tag
|
||||
func IsValidTag(tagVersion string) (bool, error) {
|
||||
if tagVersion[0] == 'v' {
|
||||
tagVersion = tagVersion[1:]
|
||||
}
|
||||
tags, err := GetVersionTags()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if tag.String() == tagVersion {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
)
|
||||
|
||||
// SemanticVersion is a struct containing a semantic version
|
||||
type SemanticVersion struct {
|
||||
Version *semver.Version
|
||||
}
|
||||
|
||||
// NewSemanticVersion creates a new SemanticVersion object with the given version string
|
||||
func NewSemanticVersion(version string) (*SemanticVersion, error) {
|
||||
semverVersion, err := semver.NewVersion(version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SemanticVersion{
|
||||
Version: semverVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsRelease returns true if it's a release version
|
||||
func (s *SemanticVersion) IsRelease() bool {
|
||||
// Limit to v2
|
||||
if s.Version.Major() != 2 {
|
||||
return false
|
||||
}
|
||||
return len(s.Version.Prerelease()) == 0 && len(s.Version.Metadata()) == 0
|
||||
}
|
||||
|
||||
// IsPreRelease returns true if it's a prerelease version
|
||||
func (s *SemanticVersion) IsPreRelease() bool {
|
||||
// Limit to v1
|
||||
if s.Version.Major() != 2 {
|
||||
return false
|
||||
}
|
||||
return len(s.Version.Prerelease()) > 0
|
||||
}
|
||||
|
||||
func (s *SemanticVersion) String() string {
|
||||
return s.Version.String()
|
||||
}
|
||||
|
||||
// IsGreaterThan returns true if this version is greater than the given version
|
||||
func (s *SemanticVersion) IsGreaterThan(version *SemanticVersion) (bool, error) {
|
||||
// Set up new constraint
|
||||
constraint, err := semver.NewConstraint("> " + version.Version.String())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if the desired one is greater than the requested on
|
||||
success, msgs := constraint.Validate(s.Version)
|
||||
if !success {
|
||||
return false, msgs[0]
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// IsGreaterThanOrEqual returns true if this version is greater than or equal the given version
|
||||
func (s *SemanticVersion) IsGreaterThanOrEqual(version *SemanticVersion) (bool, error) {
|
||||
// Set up new constraint
|
||||
constraint, err := semver.NewConstraint(">= " + version.Version.String())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if the desired one is greater than the requested on
|
||||
success, msgs := constraint.Validate(s.Version)
|
||||
if !success {
|
||||
return false, msgs[0]
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// MainVersion returns the main version of any version+prerelease+metadata
|
||||
// EG: MainVersion("1.2.3-pre") => "1.2.3"
|
||||
func (s *SemanticVersion) MainVersion() *SemanticVersion {
|
||||
mainVersion := fmt.Sprintf("%d.%d.%d", s.Version.Major(), s.Version.Minor(), s.Version.Patch())
|
||||
result, _ := NewSemanticVersion(mainVersion)
|
||||
return result
|
||||
}
|
||||
|
||||
// SemverCollection is a collection of SemanticVersion objects
|
||||
type SemverCollection []*SemanticVersion
|
||||
|
||||
// Len returns the length of a collection. The number of Version instances
|
||||
// on the slice.
|
||||
func (c SemverCollection) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
// Less is needed for the sort interface to compare two Version objects on the
|
||||
// slice. If checks if one is less than the other.
|
||||
func (c SemverCollection) Less(i, j int) bool {
|
||||
return c[i].Version.LessThan(c[j].Version)
|
||||
}
|
||||
|
||||
// Swap is needed for the sort interface to replace the Version objects
|
||||
// at two different positions in the slice.
|
||||
func (c SemverCollection) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package menumanager
|
||||
|
||||
import "github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
func (m *Manager) SetApplicationMenu(applicationMenu *menu.Menu) error {
|
||||
|
||||
if applicationMenu == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
m.applicationMenu = applicationMenu
|
||||
|
||||
// Reset the menu map
|
||||
m.applicationMenuItemMap = NewMenuItemMap()
|
||||
|
||||
// Add the menu to the menu map
|
||||
m.applicationMenuItemMap.AddMenu(applicationMenu)
|
||||
|
||||
return m.processApplicationMenu()
|
||||
}
|
||||
|
||||
func (m *Manager) GetApplicationMenuJSON() string {
|
||||
return m.applicationMenuJSON
|
||||
}
|
||||
|
||||
// UpdateApplicationMenu reprocesses the application menu to pick up structure
|
||||
// changes etc
|
||||
// Returns the JSON representation of the updated menu
|
||||
func (m *Manager) UpdateApplicationMenu() (string, error) {
|
||||
m.applicationMenuItemMap = NewMenuItemMap()
|
||||
m.applicationMenuItemMap.AddMenu(m.applicationMenu)
|
||||
err := m.processApplicationMenu()
|
||||
return m.applicationMenuJSON, err
|
||||
}
|
||||
|
||||
func (m *Manager) processApplicationMenu() error {
|
||||
|
||||
// Process the menu
|
||||
processedApplicationMenu := NewWailsMenu(m.applicationMenuItemMap, m.applicationMenu)
|
||||
applicationMenuJSON, err := processedApplicationMenu.AsJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.applicationMenuJSON = applicationMenuJSON
|
||||
return nil
|
||||
}
|
||||
@@ -1,60 +1,65 @@
|
||||
package menumanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
type ContextMenu struct {
|
||||
ID string
|
||||
ProcessedMenu *WailsMenu
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
}
|
||||
|
||||
func (t *ContextMenu) AsJSON() (string, error) {
|
||||
data, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu {
|
||||
|
||||
result := &ContextMenu{
|
||||
ID: contextMenu.ID,
|
||||
menu: contextMenu.Menu,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(contextMenu.Menu)
|
||||
result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
|
||||
|
||||
return result
|
||||
}
|
||||
import "github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
//
|
||||
//import (
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "github.com/wailsapp/wails/v2/pkg/menu"
|
||||
//)
|
||||
//
|
||||
//type ContextMenu struct {
|
||||
// ID string
|
||||
// ProcessedMenu *WailsMenu
|
||||
// menuItemMap *MenuItemMap
|
||||
// menu *menu.Menu
|
||||
//}
|
||||
//
|
||||
//func (t *ContextMenu) AsJSON() (string, error) {
|
||||
// data, err := json.Marshal(t)
|
||||
// if err != nil {
|
||||
// return "", err
|
||||
// }
|
||||
// return string(data), nil
|
||||
//}
|
||||
//
|
||||
//func (m *Manager) NewContextMenu(contextMenu *menu.ContextMenu) *ContextMenu {
|
||||
//
|
||||
// result := &ContextMenu{
|
||||
// ID: contextMenu.ID,
|
||||
// menu: contextMenu.Menu,
|
||||
// menuItemMap: NewMenuItemMap(),
|
||||
// }
|
||||
//
|
||||
// result.menuItemMap.AddMenu(contextMenu.Menu)
|
||||
// result.ProcessedMenu = m.NewWailsMenu(result.menuItemMap, result.menu)
|
||||
//
|
||||
// return result
|
||||
//}
|
||||
//
|
||||
func (m *Manager) AddContextMenu(contextMenu *menu.ContextMenu) {
|
||||
|
||||
newContextMenu := NewContextMenu(contextMenu)
|
||||
|
||||
// Save the references
|
||||
m.contextMenus[contextMenu.ID] = newContextMenu
|
||||
m.contextMenuPointers[contextMenu] = contextMenu.ID
|
||||
//
|
||||
// newContextMenu := m.NewContextMenu(contextMenu)
|
||||
//
|
||||
// // Save the references
|
||||
// m.contextMenus[contextMenu.ID] = newContextMenu
|
||||
// m.contextMenuPointers[contextMenu] = contextMenu.ID
|
||||
}
|
||||
|
||||
//
|
||||
func (m *Manager) UpdateContextMenu(contextMenu *menu.ContextMenu) (string, error) {
|
||||
contextMenuID, contextMenuKnown := m.contextMenuPointers[contextMenu]
|
||||
if !contextMenuKnown {
|
||||
return "", fmt.Errorf("unknown Context Menu '%s'. Please add the context menu using AddContextMenu()", contextMenu.ID)
|
||||
}
|
||||
|
||||
// Create the updated context menu
|
||||
updatedContextMenu := NewContextMenu(contextMenu)
|
||||
|
||||
// Save the reference
|
||||
m.contextMenus[contextMenuID] = updatedContextMenu
|
||||
|
||||
return updatedContextMenu.AsJSON()
|
||||
// contextMenuID, contextMenuKnown := m.contextMenuPointers[contextMenu]
|
||||
// if !contextMenuKnown {
|
||||
// return "", fmt.Errorf("unknown Context Menu '%s'. Please add the context menu using AddContextMenu()", contextMenu.ID)
|
||||
// }
|
||||
//
|
||||
// // Create the updated context menu
|
||||
// updatedContextMenu := m.NewContextMenu(contextMenu)
|
||||
//
|
||||
// // Save the reference
|
||||
// m.contextMenus[contextMenuID] = updatedContextMenu
|
||||
//
|
||||
// return updatedContextMenu.AsJSON()
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package menumanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MenuItemMap holds a mapping between menuIDs and menu items
|
||||
type MenuItemMap struct {
|
||||
idToMenuItemMap map[string]*menu.MenuItem
|
||||
menuItemToIDMap map[*menu.MenuItem]string
|
||||
|
||||
// We use a simple counter to keep track of unique menu IDs
|
||||
menuIDCounter int64
|
||||
menuIDCounterMutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewMenuItemMap() *MenuItemMap {
|
||||
result := &MenuItemMap{
|
||||
idToMenuItemMap: make(map[string]*menu.MenuItem),
|
||||
menuItemToIDMap: make(map[*menu.MenuItem]string),
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *MenuItemMap) AddMenu(menu *menu.Menu) {
|
||||
if menu == nil {
|
||||
return
|
||||
}
|
||||
for _, item := range menu.Items {
|
||||
m.processMenuItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MenuItemMap) Dump() {
|
||||
println("idToMenuItemMap:")
|
||||
for key, value := range m.idToMenuItemMap {
|
||||
fmt.Printf(" %s\t%p\n", key, value)
|
||||
}
|
||||
println("\nmenuItemToIDMap")
|
||||
for key, value := range m.menuItemToIDMap {
|
||||
fmt.Printf(" %p\t%s\n", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateMenuID returns a unique string ID for a menu item
|
||||
func (m *MenuItemMap) generateMenuID() string {
|
||||
m.menuIDCounterMutex.Lock()
|
||||
result := fmt.Sprintf("%d", m.menuIDCounter)
|
||||
m.menuIDCounter++
|
||||
m.menuIDCounterMutex.Unlock()
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *MenuItemMap) processMenuItem(item *menu.MenuItem) {
|
||||
|
||||
if item.SubMenu != nil {
|
||||
for _, submenuitem := range item.SubMenu.Items {
|
||||
m.processMenuItem(submenuitem)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a unique ID for this menu item
|
||||
menuID := m.generateMenuID()
|
||||
|
||||
// Store references
|
||||
m.idToMenuItemMap[menuID] = item
|
||||
m.menuItemToIDMap[item] = menuID
|
||||
}
|
||||
|
||||
func (m *MenuItemMap) getMenuItemByID(menuId string) *menu.MenuItem {
|
||||
return m.idToMenuItemMap[menuId]
|
||||
}
|
||||
@@ -2,89 +2,53 @@ package menumanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/internal/counter"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
|
||||
// The application menu.
|
||||
applicationMenu *menu.Menu
|
||||
applicationMenuJSON string
|
||||
// MenuItemMap is a map of all menu items against a generated ID
|
||||
menuItemMap map[string]*menu.MenuItem
|
||||
menuItemIDCounter *counter.Counter
|
||||
processedMenuItems map[*menu.MenuItem]*ProcessedMenuItem
|
||||
|
||||
// Our application menu mappings
|
||||
applicationMenuItemMap *MenuItemMap
|
||||
// Menus
|
||||
menuIDCounter *counter.Counter
|
||||
|
||||
// Context menus
|
||||
contextMenus map[string]*ContextMenu
|
||||
contextMenuPointers map[*menu.ContextMenu]string
|
||||
|
||||
// Tray menu stores
|
||||
trayMenus map[string]*TrayMenu
|
||||
trayMenuPointers map[*menu.TrayMenu]string
|
||||
// Map wails menus to internal menus
|
||||
trayMenuMap map[*menu.TrayMenu]*TrayMenu
|
||||
trayMenuIDCounter *counter.Counter
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
applicationMenuItemMap: NewMenuItemMap(),
|
||||
contextMenus: make(map[string]*ContextMenu),
|
||||
contextMenuPointers: make(map[*menu.ContextMenu]string),
|
||||
trayMenus: make(map[string]*TrayMenu),
|
||||
trayMenuPointers: make(map[*menu.TrayMenu]string),
|
||||
trayMenuMap: make(map[*menu.TrayMenu]*TrayMenu),
|
||||
trayMenuIDCounter: counter.NewCounter(0),
|
||||
menuIDCounter: counter.NewCounter(0),
|
||||
menuItemMap: make(map[string]*menu.MenuItem),
|
||||
menuItemIDCounter: counter.NewCounter(0),
|
||||
processedMenuItems: make(map[*menu.MenuItem]*ProcessedMenuItem),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) getMenuItemByID(menuMap *MenuItemMap, menuId string) *menu.MenuItem {
|
||||
return menuMap.idToMenuItemMap[menuId]
|
||||
}
|
||||
|
||||
func (m *Manager) ProcessClick(menuID string, data string, menuType string, parentID string) error {
|
||||
|
||||
var menuItemMap *MenuItemMap
|
||||
|
||||
switch menuType {
|
||||
case "ApplicationMenu":
|
||||
menuItemMap = m.applicationMenuItemMap
|
||||
case "ContextMenu":
|
||||
contextMenu := m.contextMenus[parentID]
|
||||
if contextMenu == nil {
|
||||
return fmt.Errorf("unknown context menu: %s", parentID)
|
||||
}
|
||||
menuItemMap = contextMenu.menuItemMap
|
||||
case "TrayMenu":
|
||||
trayMenu := m.trayMenus[parentID]
|
||||
if trayMenu == nil {
|
||||
return fmt.Errorf("unknown tray menu: %s", parentID)
|
||||
}
|
||||
menuItemMap = trayMenu.menuItemMap
|
||||
default:
|
||||
return fmt.Errorf("unknown menutype: %s", menuType)
|
||||
}
|
||||
|
||||
// Get the menu item
|
||||
menuItem := menuItemMap.getMenuItemByID(menuID)
|
||||
func (m *Manager) ProcessClick(menuID string, data string) error {
|
||||
// Get item from callback map
|
||||
menuItem := m.menuItemMap[menuID]
|
||||
if menuItem == nil {
|
||||
return fmt.Errorf("Cannot process menuid %s - unknown", menuID)
|
||||
}
|
||||
|
||||
// Is the menu item a checkbox?
|
||||
if menuItem.Type == menu.CheckboxType {
|
||||
// Toggle state
|
||||
menuItem.Checked = !menuItem.Checked
|
||||
return fmt.Errorf("menuItem doesn't exist")
|
||||
}
|
||||
|
||||
if menuItem.Click == nil {
|
||||
// No callback
|
||||
return fmt.Errorf("No callback for menu '%s'", menuItem.Label)
|
||||
return fmt.Errorf("menuItem 'does not have a callback")
|
||||
}
|
||||
|
||||
// Create new Callback struct
|
||||
callbackData := &menu.CallbackData{
|
||||
MenuItem: menuItem,
|
||||
ContextData: data,
|
||||
}
|
||||
|
||||
// Call back!
|
||||
// Callback!
|
||||
go menuItem.Click(callbackData)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,83 +2,165 @@ package menumanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
|
||||
type ProcessedMenuItem struct {
|
||||
ID string
|
||||
// Label is what appears as the menu text
|
||||
Label string `json:",omitempty"`
|
||||
// Role is a predefined menu type
|
||||
Role menu.Role `json:",omitempty"`
|
||||
// Accelerator holds a representation of a key binding
|
||||
Accelerator *keys.Accelerator `json:",omitempty"`
|
||||
// Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu
|
||||
Type menu.Type
|
||||
// Disabled makes the item unselectable
|
||||
Disabled bool `json:",omitempty"`
|
||||
// Hidden ensures that the item is not shown in the menu
|
||||
Hidden bool `json:",omitempty"`
|
||||
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
|
||||
Checked bool `json:",omitempty"`
|
||||
// Submenu contains a list of menu items that will be shown as a submenu
|
||||
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
|
||||
SubMenu *ProcessedMenu `json:",omitempty"`
|
||||
type ProcessedMenu struct {
|
||||
ID string `json:"I"`
|
||||
|
||||
// Colour
|
||||
RGBA string `json:",omitempty"`
|
||||
|
||||
// Font
|
||||
FontSize int `json:",omitempty"`
|
||||
FontName string `json:",omitempty"`
|
||||
|
||||
// Image - base64 image data
|
||||
Image string `json:",omitempty"`
|
||||
|
||||
// Tooltip
|
||||
Tooltip string `json:",omitempty"`
|
||||
Items []*ProcessedMenuItem `json:"i,omitempty"`
|
||||
RadioGroups []*RadioGroup `json:"r,omitempty"`
|
||||
}
|
||||
|
||||
func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem {
|
||||
func (m *Manager) ProcessMenu(menu *menu.Menu) *ProcessedMenu {
|
||||
wm := m.NewWailsMenu(menu)
|
||||
|
||||
if len(wm.Menu) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ProcessedMenu{
|
||||
ID: m.generateMenuID(),
|
||||
Items: wm.Menu,
|
||||
RadioGroups: wm.RadioGroups,
|
||||
}
|
||||
}
|
||||
|
||||
type ProcessedMenuItem struct {
|
||||
// ID of the menu item
|
||||
ID string `json:"I"`
|
||||
|
||||
// Label is what appears as the menu text
|
||||
Label string `json:"l,omitempty"`
|
||||
|
||||
// AlternateLabel is a secondary label (Used by Mac)
|
||||
AlternateLabel string `json:"L,omitempty"`
|
||||
|
||||
// Role is a predefined menu type
|
||||
Role menu.Role `json:"r,omitempty"`
|
||||
|
||||
// Accelerator holds a representation of a key binding
|
||||
Accelerator *keys.Accelerator `json:"a,omitempty"`
|
||||
|
||||
// Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu
|
||||
Type menu.Type `json:"t,omitempty"`
|
||||
|
||||
// Font to use for the menu item
|
||||
Font string `json:"f,omitempty"`
|
||||
|
||||
// Font to use for the menu item
|
||||
FontSize int `json:"F,omitempty"`
|
||||
|
||||
// RGBA is the colour of the menu item
|
||||
RGBA string `json:"R,omitempty"`
|
||||
|
||||
// Image is an image for the menu item (base64 string)
|
||||
Image string `json:"i,omitempty"`
|
||||
|
||||
// Disabled makes the item unselectable
|
||||
Disabled *bool `json:"d,omitempty"`
|
||||
|
||||
// Hidden ensures that the item is not shown in the menu
|
||||
Hidden *bool `json:"h,omitempty"`
|
||||
|
||||
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
|
||||
Checked *bool `json:"c,omitempty"`
|
||||
|
||||
// Submenu contains a list of menu items that will be shown as a submenu
|
||||
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
|
||||
SubMenu []*ProcessedMenuItem `json:"s,omitempty"`
|
||||
|
||||
// Indicates if this item has a callback
|
||||
HasCallback *bool `json:"C,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Manager) generateMenuItemID() string {
|
||||
return fmt.Sprintf("%d", m.menuItemIDCounter.Increment())
|
||||
}
|
||||
|
||||
func (m *Manager) generateMenuID() string {
|
||||
return fmt.Sprintf("%d", m.menuIDCounter.Increment())
|
||||
}
|
||||
|
||||
func (m *Manager) NewProcessedMenuItem(menuItem *menu.MenuItem) *ProcessedMenuItem {
|
||||
|
||||
// Check if this menu has already been processed.
|
||||
// This is to prevent duplicates.
|
||||
existingMenuItem := m.processedMenuItems[menuItem]
|
||||
if existingMenuItem != nil {
|
||||
return &ProcessedMenuItem{ID: existingMenuItem.ID}
|
||||
}
|
||||
|
||||
ID := m.generateMenuItemID()
|
||||
|
||||
ID := menuItemMap.menuItemToIDMap[menuItem]
|
||||
result := &ProcessedMenuItem{
|
||||
ID: ID,
|
||||
Label: menuItem.Label,
|
||||
Role: menuItem.Role,
|
||||
Accelerator: menuItem.Accelerator,
|
||||
Type: menuItem.Type,
|
||||
Disabled: menuItem.Disabled,
|
||||
Hidden: menuItem.Hidden,
|
||||
Checked: menuItem.Checked,
|
||||
SubMenu: nil,
|
||||
RGBA: menuItem.RGBA,
|
||||
FontSize: menuItem.FontSize,
|
||||
FontName: menuItem.FontName,
|
||||
Image: menuItem.Image,
|
||||
Tooltip: menuItem.Tooltip,
|
||||
ID: ID,
|
||||
Label: menuItem.Label,
|
||||
AlternateLabel: menuItem.AlternateLabel,
|
||||
Role: menuItem.Role,
|
||||
Accelerator: menuItem.Accelerator,
|
||||
Type: menuItem.Type,
|
||||
Font: menuItem.Font,
|
||||
FontSize: menuItem.FontSize,
|
||||
RGBA: menuItem.RGBA,
|
||||
Image: menuItem.Image,
|
||||
Disabled: nil,
|
||||
Hidden: nil,
|
||||
Checked: nil,
|
||||
SubMenu: nil,
|
||||
HasCallback: nil,
|
||||
}
|
||||
|
||||
if menuItem.Hidden {
|
||||
result.Hidden = new(bool)
|
||||
*result.Hidden = true
|
||||
}
|
||||
|
||||
if menuItem.Disabled {
|
||||
result.Disabled = new(bool)
|
||||
*result.Disabled = true
|
||||
}
|
||||
|
||||
if menuItem.Checked {
|
||||
result.Checked = new(bool)
|
||||
*result.Checked = true
|
||||
}
|
||||
|
||||
if menuItem.Click != nil {
|
||||
result.HasCallback = new(bool)
|
||||
*result.HasCallback = true
|
||||
}
|
||||
|
||||
if menuItem.SubMenu != nil {
|
||||
result.SubMenu = NewProcessedMenu(menuItemMap, menuItem.SubMenu)
|
||||
result.SubMenu = m.NewProcessedMenu(menuItem.SubMenu)
|
||||
}
|
||||
|
||||
// Add menu item to item map
|
||||
m.menuItemMap[ID] = menuItem
|
||||
|
||||
// Add processed Item to processedMenuItems
|
||||
m.processedMenuItems[menuItem] = result
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type ProcessedMenu struct {
|
||||
Items []*ProcessedMenuItem
|
||||
}
|
||||
func (m *Manager) NewProcessedMenu(menu *menu.Menu) []*ProcessedMenuItem {
|
||||
|
||||
func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu {
|
||||
if menu == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := &ProcessedMenu{}
|
||||
if menu != nil {
|
||||
for _, item := range menu.Items {
|
||||
processedMenuItem := NewProcessedMenuItem(menuItemMap, item)
|
||||
result.Items = append(result.Items, processedMenuItem)
|
||||
}
|
||||
if menu.Items == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result []*ProcessedMenuItem
|
||||
|
||||
for _, item := range menu.Items {
|
||||
processedMenuItem := m.NewProcessedMenuItem(item)
|
||||
result = append(result, processedMenuItem)
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -87,8 +169,8 @@ func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu
|
||||
// WailsMenu is the original menu with the addition
|
||||
// of radio groups extracted from the menu data
|
||||
type WailsMenu struct {
|
||||
Menu *ProcessedMenu
|
||||
RadioGroups []*RadioGroup
|
||||
Menu []*ProcessedMenuItem `json:",omitempty"`
|
||||
RadioGroups []*RadioGroup `json:",omitempty"`
|
||||
currentRadioGroup []string
|
||||
}
|
||||
|
||||
@@ -98,11 +180,12 @@ type RadioGroup struct {
|
||||
Length int
|
||||
}
|
||||
|
||||
func NewWailsMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *WailsMenu {
|
||||
func (m *Manager) NewWailsMenu(menu *menu.Menu) *WailsMenu {
|
||||
|
||||
result := &WailsMenu{}
|
||||
|
||||
// Process the menus
|
||||
result.Menu = NewProcessedMenu(menuItemMap, menu)
|
||||
result.Menu = m.NewProcessedMenu(menu)
|
||||
|
||||
// Process the radio groups
|
||||
result.processRadioGroups()
|
||||
@@ -119,17 +202,7 @@ func (w *WailsMenu) AsJSON() (string, error) {
|
||||
return string(menuAsJSON), nil
|
||||
}
|
||||
|
||||
func (w *WailsMenu) processRadioGroups() {
|
||||
// Loop over top level menus
|
||||
for _, item := range w.Menu.Items {
|
||||
// Process MenuItem
|
||||
w.processMenuItem(item)
|
||||
}
|
||||
|
||||
w.finaliseRadioGroup()
|
||||
}
|
||||
|
||||
func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
|
||||
func (w *WailsMenu) processMenuItemForRadioGroups(item *ProcessedMenuItem) {
|
||||
|
||||
switch item.Type {
|
||||
|
||||
@@ -140,8 +213,8 @@ func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
|
||||
w.finaliseRadioGroup()
|
||||
|
||||
// Process each submenu item
|
||||
for _, subitem := range item.SubMenu.Items {
|
||||
w.processMenuItem(subitem)
|
||||
for _, subitem := range item.SubMenu {
|
||||
w.processMenuItemForRadioGroups(subitem)
|
||||
}
|
||||
case menu.RadioType:
|
||||
// Add the item to the radio group
|
||||
@@ -151,6 +224,20 @@ func (w *WailsMenu) processMenuItem(item *ProcessedMenuItem) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WailsMenu) processRadioGroups() {
|
||||
|
||||
if w.Menu == nil {
|
||||
return
|
||||
}
|
||||
// Loop over top level menus
|
||||
for _, item := range w.Menu {
|
||||
// Process MenuItem
|
||||
w.processMenuItemForRadioGroups(item)
|
||||
}
|
||||
|
||||
w.finaliseRadioGroup()
|
||||
}
|
||||
|
||||
func (w *WailsMenu) finaliseRadioGroup() {
|
||||
|
||||
// If we were processing a radio group, fix up the references
|
||||
|
||||
@@ -3,29 +3,14 @@ package menumanager
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var trayMenuID int
|
||||
var trayMenuIDMutex sync.Mutex
|
||||
|
||||
func generateTrayID() string {
|
||||
trayMenuIDMutex.Lock()
|
||||
result := fmt.Sprintf("%d", trayMenuID)
|
||||
trayMenuID++
|
||||
trayMenuIDMutex.Unlock()
|
||||
return result
|
||||
}
|
||||
|
||||
type TrayMenu struct {
|
||||
ID string
|
||||
Label string
|
||||
Icon string
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
ProcessedMenu *WailsMenu
|
||||
ID string `json:"I"`
|
||||
Label string `json:"l,omitempty"`
|
||||
Icon string `json:"i,omitempty"`
|
||||
Menu *ProcessedMenu `json:"m,omitempty"`
|
||||
}
|
||||
|
||||
func (t *TrayMenu) AsJSON() (string, error) {
|
||||
@@ -36,55 +21,44 @@ func (t *TrayMenu) AsJSON() (string, error) {
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
func (m *Manager) newTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
|
||||
result := &TrayMenu{
|
||||
Label: trayMenu.Label,
|
||||
Icon: trayMenu.Icon,
|
||||
menu: trayMenu.Menu,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
result := TrayMenu{
|
||||
ID: m.generateTrayID(),
|
||||
Label: trayMenu.Label,
|
||||
Icon: trayMenu.Icon,
|
||||
Menu: m.ProcessMenu(trayMenu.Menu),
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||
result.ProcessedMenu = NewWailsMenu(result.menuItemMap, result.menu)
|
||||
return &result
|
||||
}
|
||||
|
||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
|
||||
result := m.newTrayMenu(trayMenu)
|
||||
|
||||
// Add to map
|
||||
m.trayMenuMap[trayMenu] = result
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
newTrayMenu := NewTrayMenu(trayMenu)
|
||||
|
||||
// Hook up a new ID
|
||||
trayID := generateTrayID()
|
||||
newTrayMenu.ID = trayID
|
||||
|
||||
// Save the references
|
||||
m.trayMenus[trayID] = newTrayMenu
|
||||
m.trayMenuPointers[trayMenu] = trayID
|
||||
|
||||
return newTrayMenu.AsJSON()
|
||||
func (m *Manager) generateTrayID() string {
|
||||
return fmt.Sprintf("T%d", m.trayMenuIDCounter.Increment())
|
||||
}
|
||||
|
||||
// SetTrayMenu updates or creates a menu
|
||||
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
if !trayMenuKnown {
|
||||
return m.AddTrayMenu(trayMenu)
|
||||
func (m *Manager) GetTrayMenus() ([]*TrayMenu, error) {
|
||||
var result []*TrayMenu
|
||||
for _, trayMenu := range m.trayMenuMap {
|
||||
result = append(result, trayMenu)
|
||||
}
|
||||
|
||||
// Create the updated tray menu
|
||||
updatedTrayMenu := NewTrayMenu(trayMenu)
|
||||
updatedTrayMenu.ID = trayID
|
||||
|
||||
// Save the reference
|
||||
m.trayMenus[trayID] = updatedTrayMenu
|
||||
|
||||
return updatedTrayMenu.AsJSON()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetTrayMenus() ([]string, error) {
|
||||
result := []string{}
|
||||
for _, trayMenu := range m.trayMenus {
|
||||
func (m *Manager) GetTrayMenusAsJSON() ([]string, error) {
|
||||
var result []string
|
||||
for _, trayMenu := range m.trayMenuMap {
|
||||
JSON, err := trayMenu.AsJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -95,40 +69,11 @@ func (m *Manager) GetTrayMenus() ([]string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SetTrayMenu updates or creates a menu
|
||||
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
if !trayMenuKnown {
|
||||
return "", fmt.Errorf("[UpdateTrayMenuLabel] unknown tray id for tray %s", trayMenu.Label)
|
||||
}
|
||||
|
||||
type LabelUpdate struct {
|
||||
ID string
|
||||
Label string
|
||||
}
|
||||
|
||||
update := &LabelUpdate{
|
||||
ID: trayID,
|
||||
Label: trayMenu.Label,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(update)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "[UpdateTrayMenuLabel] ")
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
|
||||
}
|
||||
|
||||
func (m *Manager) GetContextMenus() ([]string, error) {
|
||||
result := []string{}
|
||||
for _, contextMenu := range m.contextMenus {
|
||||
JSON, err := contextMenu.AsJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, JSON)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return "", nil
|
||||
}
|
||||
|
||||
99
v2/internal/menumanager/traymenu_test.go
Normal file
99
v2/internal/menumanager/traymenu_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package menumanager
|
||||
|
||||
import (
|
||||
"github.com/matryer/is"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestManager_AddTrayMenu(t *testing.T) {
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
simpleLabel := menu.Text("test", nil, nil)
|
||||
checkbox := menu.Checkbox("test", true, nil, nil)
|
||||
radioGroup1 := menu.Radio("option 1", true, nil, nil)
|
||||
radioGroup2 := menu.Radio("option 2", false, nil, nil)
|
||||
radioGroup3 := menu.Radio("option 3", false, nil, nil)
|
||||
callback := menu.Text("Preferences", nil, func(_ *menu.CallbackData) {})
|
||||
|
||||
empty := &menu.TrayMenu{}
|
||||
labelOnly := &menu.TrayMenu{Label: "test"}
|
||||
iconOnly := &menu.TrayMenu{Icon: "svelte"}
|
||||
iconLabel := &menu.TrayMenu{Icon: "svelte", Label: "test"}
|
||||
blankLabel := &menu.TrayMenu{Label: ""}
|
||||
blankMenu := &menu.TrayMenu{Menu: menu.NewMenu()}
|
||||
menuTextItem := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabel)}
|
||||
checkboxItem := &menu.TrayMenu{Menu: menu.NewMenuFromItems(checkbox)}
|
||||
radioGroupItems := &menu.TrayMenu{Menu: menu.NewMenuFromItems(radioGroup1, radioGroup2, radioGroup3)}
|
||||
callbackItem := &menu.TrayMenu{Menu: menu.NewMenuFromItems(callback)}
|
||||
|
||||
tests := []struct {
|
||||
trayMenu *menu.TrayMenu
|
||||
want string
|
||||
}{
|
||||
{empty, "{\"I\":\"T1\"}"},
|
||||
{labelOnly, "{\"I\":\"T1\",\"l\":\"test\"}"},
|
||||
{iconOnly, "{\"I\":\"T1\",\"i\":\"svelte\"}"},
|
||||
{iconLabel, "{\"I\":\"T1\",\"l\":\"test\",\"i\":\"svelte\"}"},
|
||||
{blankLabel, "{\"I\":\"T1\"}"},
|
||||
{blankMenu, "{\"I\":\"T1\"}"},
|
||||
{menuTextItem, "{\"I\":\"T1\",\"m\":{\"I\":\"1\",\"i\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\"}]}}"},
|
||||
{checkboxItem, "{\"I\":\"T1\",\"m\":{\"I\":\"1\",\"i\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"c\",\"c\":true}]}}"},
|
||||
{radioGroupItems, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"option 1\",\"t\":\"r\",\"c\":true},{\"I\":\"2\",\"l\":\"option 2\",\"t\":\"r\"},{\"I\":\"3\",\"l\":\"option 3\",\"t\":\"r\"}],\"r\":[{\"Members\":[\"1\",\"2\",\"3\"],\"Length\":3}]}"},
|
||||
{callbackItem, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"Preferences\",\"t\":\"t\",\"C\":true}]}"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
m := NewManager()
|
||||
got := m.AddTrayMenu(tt.trayMenu)
|
||||
JSON, err := got.AsJSON()
|
||||
is.NoErr(err)
|
||||
is.Equal(JSON, tt.want)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestManager_CallbackMap(t *testing.T) {
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
simpleLabel := menu.Text("test", nil, nil)
|
||||
simpleLabelWithCallback := menu.Text("test", nil, func(_ *menu.CallbackData) {})
|
||||
checkboxWithCallback := menu.Checkbox("test", true, nil, func(_ *menu.CallbackData) {})
|
||||
submenu := menu.SubMenu("test", menu.NewMenuFromItems(checkboxWithCallback))
|
||||
|
||||
blankMenu := &menu.TrayMenu{Menu: menu.NewMenu()}
|
||||
noCallback := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabel)}
|
||||
oneMenuWithCallback := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback)}
|
||||
duplicateCallbacks := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback, simpleLabelWithCallback)}
|
||||
twoMenusWithCallbacks := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback, checkboxWithCallback)}
|
||||
duplicateMenusWithCallbacks := &menu.TrayMenu{Menu: menu.NewMenuFromItems(simpleLabelWithCallback, checkboxWithCallback, simpleLabelWithCallback, checkboxWithCallback)}
|
||||
submenuWithCallback := &menu.TrayMenu{Menu: menu.NewMenuFromItems(submenu)}
|
||||
duplicateSubmenus := &menu.TrayMenu{Menu: menu.NewMenuFromItems(submenu, submenu)}
|
||||
|
||||
tests := []struct {
|
||||
trayMenu *menu.TrayMenu
|
||||
trays int
|
||||
menuItems int
|
||||
JSON string
|
||||
}{
|
||||
{blankMenu, 1, 0, "{\"I\":\"T1\"}"},
|
||||
{noCallback, 1, 1, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\"}]}"},
|
||||
{oneMenuWithCallback, 1, 1, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true}]}"},
|
||||
{duplicateCallbacks, 1, 1, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true},{\"I\":\"1\"}]}"},
|
||||
{twoMenusWithCallbacks, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true},{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true}]}"},
|
||||
{duplicateMenusWithCallbacks, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"t\",\"C\":true},{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true},{\"I\":\"1\"},{\"I\":\"2\"}]}"},
|
||||
{submenuWithCallback, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"S\",\"s\":[{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true}]}]}"},
|
||||
{duplicateSubmenus, 1, 2, "{\"I\":\"T1\",\"m\":[{\"I\":\"1\",\"l\":\"test\",\"t\":\"S\",\"s\":[{\"I\":\"2\",\"l\":\"test\",\"t\":\"c\",\"c\":true,\"C\":true}]},{\"I\":\"1\"}]}"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
m := NewManager()
|
||||
tm := m.AddTrayMenu(test.trayMenu)
|
||||
is.Equal(len(m.trayMenuMap), test.trays)
|
||||
is.Equal(len(m.menuItemMap), test.menuItems)
|
||||
JSON, err := tm.AsJSON()
|
||||
is.NoErr(err)
|
||||
is.Equal(JSON, test.JSON)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package messagedispatcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
@@ -27,8 +26,6 @@ type Client interface {
|
||||
WindowUnminimise()
|
||||
WindowPosition(x int, y int)
|
||||
WindowSize(width int, height int)
|
||||
WindowSetMinSize(width int, height int)
|
||||
WindowSetMaxSize(width int, height int)
|
||||
WindowFullscreen()
|
||||
WindowUnFullscreen()
|
||||
WindowSetColour(colour int)
|
||||
|
||||
@@ -2,12 +2,11 @@ package messagedispatcher
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/crypto"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
@@ -350,38 +349,6 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowSize(w, h)
|
||||
}
|
||||
case "minsize":
|
||||
// We need 2 arguments
|
||||
if len(splitTopic) != 4 {
|
||||
d.logger.Error("Invalid number of parameters for 'window:minsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
w, err1 := strconv.Atoi(splitTopic[2])
|
||||
h, err2 := strconv.Atoi(splitTopic[3])
|
||||
if err1 != nil || err2 != nil {
|
||||
d.logger.Error("Invalid integer parameters for 'window:minsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// Notifh clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowSetMinSize(w, h)
|
||||
}
|
||||
case "maxsize":
|
||||
// We need 2 arguments
|
||||
if len(splitTopic) != 4 {
|
||||
d.logger.Error("Invalid number of parameters for 'window:maxsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
w, err1 := strconv.Atoi(splitTopic[2])
|
||||
h, err2 := strconv.Atoi(splitTopic[3])
|
||||
if err1 != nil || err2 != nil {
|
||||
d.logger.Error("Invalid integer parameters for 'window:maxsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// Notifh clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowSetMaxSize(w, h)
|
||||
}
|
||||
default:
|
||||
d.logger.Error("Unknown window command: %s", command)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Proces
|
||||
// Start the process
|
||||
func (p *Process) Start() error {
|
||||
|
||||
p.cmd.
|
||||
err := p.cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
1
v2/internal/runtime/assets/desktop.js
Normal file
1
v2/internal/runtime/assets/desktop.js
Normal file
File diff suppressed because one or more lines are too long
@@ -10,7 +10,6 @@ The lightweight framework for web-like apps
|
||||
/* jshint esversion: 6 */
|
||||
import { SetBindings } from './bindings';
|
||||
import { Init } from './main';
|
||||
import {RaiseError} from '../desktop/darwin';
|
||||
|
||||
// Setup global error handler
|
||||
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
@@ -22,7 +21,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
error: JSON.stringify(error),
|
||||
stack: function() { return JSON.stringify(new Error().stack); }(),
|
||||
};
|
||||
RaiseError(errorMessage);
|
||||
window.wails.Log.Error(JSON.stringify(errorMessage));
|
||||
};
|
||||
|
||||
// Initialise the Runtime
|
||||
|
||||
@@ -25,10 +25,6 @@ export function SendMessage(message) {
|
||||
window.webkit.messageHandlers.external.postMessage(message);
|
||||
}
|
||||
|
||||
export function RaiseError(message) {
|
||||
window.webkit.messageHandlers.error.postMessage(message);
|
||||
}
|
||||
|
||||
export function Init() {
|
||||
|
||||
// Setup drag handler
|
||||
|
||||
@@ -13,7 +13,7 @@ module.exports = {
|
||||
mode: 'production',
|
||||
output: {
|
||||
path: path.resolve(__dirname, '..', 'assets'),
|
||||
filename: 'desktop_'+platform+'.js',
|
||||
filename: 'desktop.js',
|
||||
library: 'Wails'
|
||||
},
|
||||
resolve: {
|
||||
|
||||
@@ -3,13 +3,12 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"github.com/wailsapp/wails/v2/internal/shell"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"github.com/wailsapp/wails/v2/internal/shell"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -50,7 +49,7 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop_" + platform + ".js")
|
||||
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop.js")
|
||||
runtimeData, err := ioutil.ReadFile(wailsJS)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
@@ -6,12 +6,9 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
golog "log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/deepcopy"
|
||||
)
|
||||
|
||||
// Options defines the optional data that may be used
|
||||
@@ -67,31 +64,21 @@ func fatal(err error) {
|
||||
// New creates a new store
|
||||
func (p *StoreProvider) New(name string, defaultValue interface{}) *Store {
|
||||
|
||||
if defaultValue == nil {
|
||||
golog.Fatal("Cannot initialise a store with nil")
|
||||
}
|
||||
dataType := reflect.TypeOf(defaultValue)
|
||||
|
||||
result := Store{
|
||||
name: name,
|
||||
runtime: p.runtime,
|
||||
name: name,
|
||||
runtime: p.runtime,
|
||||
data: reflect.ValueOf(defaultValue),
|
||||
dataType: dataType,
|
||||
}
|
||||
|
||||
// Setup the sync listener
|
||||
result.setupListener()
|
||||
|
||||
result.Set(defaultValue)
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
func (s *Store) lock() {
|
||||
s.mux.Lock()
|
||||
}
|
||||
|
||||
func (s *Store) unlock() {
|
||||
s.mux.Unlock()
|
||||
}
|
||||
|
||||
// OnError takes a function that will be called
|
||||
// whenever an error occurs
|
||||
func (s *Store) OnError(callback func(error)) {
|
||||
@@ -118,7 +105,7 @@ func (s *Store) processUpdatedData(data string) error {
|
||||
}
|
||||
|
||||
// Lock mutex for writing
|
||||
s.lock()
|
||||
s.mux.Lock()
|
||||
|
||||
// Handle nulls
|
||||
if newData == nil {
|
||||
@@ -129,7 +116,7 @@ func (s *Store) processUpdatedData(data string) error {
|
||||
}
|
||||
|
||||
// Unlock mutex
|
||||
s.unlock()
|
||||
s.mux.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -159,34 +146,20 @@ func (s *Store) setupListener() {
|
||||
// Resetting the curent data will resync
|
||||
s.resync()
|
||||
})
|
||||
|
||||
// Do initial resync
|
||||
s.resync()
|
||||
}
|
||||
|
||||
func (s *Store) resync() {
|
||||
|
||||
// Lock
|
||||
s.lock()
|
||||
defer s.unlock()
|
||||
|
||||
var result string
|
||||
|
||||
if s.data.IsValid() {
|
||||
rawdata, err := json.Marshal(s.data.Interface())
|
||||
if err != nil {
|
||||
if s.errorHandler != nil {
|
||||
s.errorHandler(err)
|
||||
return
|
||||
}
|
||||
// Stringify data
|
||||
newdata, err := json.Marshal(s.data.Interface())
|
||||
if err != nil {
|
||||
if s.errorHandler != nil {
|
||||
s.errorHandler(err)
|
||||
return
|
||||
}
|
||||
result = string(rawdata)
|
||||
} else {
|
||||
result = "{}"
|
||||
}
|
||||
|
||||
// Emit event to front end
|
||||
s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, result)
|
||||
s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, string(newdata))
|
||||
|
||||
// Notify subscribers
|
||||
s.notify()
|
||||
@@ -199,9 +172,7 @@ func (s *Store) notify() {
|
||||
for _, callback := range s.callbacks {
|
||||
|
||||
// Build args
|
||||
s.lock()
|
||||
args := []reflect.Value{s.data}
|
||||
s.unlock()
|
||||
|
||||
if s.notifySynchronously {
|
||||
callback.Call(args)
|
||||
@@ -216,31 +187,16 @@ func (s *Store) notify() {
|
||||
// and notify listeners of the change
|
||||
func (s *Store) Set(data interface{}) error {
|
||||
|
||||
if data == nil {
|
||||
return fmt.Errorf("cannot set store to nil")
|
||||
}
|
||||
inType := reflect.TypeOf(data)
|
||||
|
||||
s.lock()
|
||||
|
||||
dataCopy := deepcopy.Copy(data)
|
||||
|
||||
if dataCopy != nil {
|
||||
inType := reflect.TypeOf(dataCopy)
|
||||
|
||||
if inType != s.dataType && s.data.IsValid() {
|
||||
s.unlock()
|
||||
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
|
||||
}
|
||||
}
|
||||
|
||||
if s.dataType == nil {
|
||||
s.dataType = reflect.TypeOf(dataCopy)
|
||||
if inType != s.dataType {
|
||||
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
|
||||
}
|
||||
|
||||
// Save data
|
||||
s.data = reflect.ValueOf(dataCopy)
|
||||
|
||||
s.unlock()
|
||||
s.mux.Lock()
|
||||
s.data = reflect.ValueOf(data)
|
||||
s.mux.Unlock()
|
||||
|
||||
// Resync with subscribers
|
||||
s.resync()
|
||||
@@ -291,9 +247,7 @@ func (s *Store) Subscribe(callback interface{}) {
|
||||
|
||||
callbackFunc := reflect.ValueOf(callback)
|
||||
|
||||
s.lock()
|
||||
s.callbacks = append(s.callbacks, callbackFunc)
|
||||
s.unlock()
|
||||
}
|
||||
|
||||
// updaterCheck ensures the given function to Update() is
|
||||
@@ -343,9 +297,7 @@ func (s *Store) Update(updater interface{}) {
|
||||
}
|
||||
|
||||
// Build args
|
||||
s.lock()
|
||||
args := []reflect.Value{s.data}
|
||||
s.unlock()
|
||||
|
||||
// Make call
|
||||
results := reflect.ValueOf(updater).Call(args)
|
||||
@@ -356,12 +308,5 @@ func (s *Store) Update(updater interface{}) {
|
||||
|
||||
// Get returns the value of the data that's kept in the current state / Store
|
||||
func (s *Store) Get() interface{} {
|
||||
s.lock()
|
||||
defer s.unlock()
|
||||
|
||||
if !s.data.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.data.Interface()
|
||||
}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
internallogger "github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
|
||||
is2 "github.com/matryer/is"
|
||||
)
|
||||
|
||||
func TestStoreProvider_NewWithNilDefault(t *testing.T) {
|
||||
is := is2.New(t)
|
||||
|
||||
defaultLogger := logger.NewDefaultLogger()
|
||||
testLogger := internallogger.New(defaultLogger)
|
||||
//testLogger.SetLogLevel(logger.TRACE)
|
||||
serviceBus := servicebus.New(testLogger)
|
||||
err := serviceBus.Start()
|
||||
is.NoErr(err)
|
||||
defer serviceBus.Stop()
|
||||
|
||||
testRuntime := New(serviceBus)
|
||||
storeProvider := newStore(testRuntime)
|
||||
|
||||
testStore := storeProvider.New("test", 0)
|
||||
|
||||
// You should be able to write a new value into a
|
||||
// store initialised with nil
|
||||
err = testStore.Set(100)
|
||||
is.NoErr(err)
|
||||
|
||||
// You shouldn't be able to write different types to the
|
||||
// store
|
||||
err = testStore.Set(false)
|
||||
is.True(err != nil)
|
||||
}
|
||||
|
||||
func TestStoreProvider_NewWithScalarDefault(t *testing.T) {
|
||||
is := is2.New(t)
|
||||
|
||||
defaultLogger := logger.NewDefaultLogger()
|
||||
testLogger := internallogger.New(defaultLogger)
|
||||
//testLogger.SetLogLevel(logger.TRACE)
|
||||
serviceBus := servicebus.New(testLogger)
|
||||
err := serviceBus.Start()
|
||||
is.NoErr(err)
|
||||
defer serviceBus.Stop()
|
||||
|
||||
testRuntime := New(serviceBus)
|
||||
storeProvider := newStore(testRuntime)
|
||||
testStore := storeProvider.New("test", 100)
|
||||
value := testStore.Get()
|
||||
is.Equal(value, 100)
|
||||
testStore.resync()
|
||||
value = testStore.Get()
|
||||
is.Equal(value, 100)
|
||||
}
|
||||
|
||||
func TestStoreProvider_NewWithStructDefault(t *testing.T) {
|
||||
is := is2.New(t)
|
||||
|
||||
defaultLogger := logger.NewDefaultLogger()
|
||||
testLogger := internallogger.New(defaultLogger)
|
||||
//testLogger.SetLogLevel(logger.TRACE)
|
||||
serviceBus := servicebus.New(testLogger)
|
||||
err := serviceBus.Start()
|
||||
is.NoErr(err)
|
||||
defer serviceBus.Stop()
|
||||
|
||||
testRuntime := New(serviceBus)
|
||||
storeProvider := newStore(testRuntime)
|
||||
|
||||
type TestValue struct {
|
||||
Name string
|
||||
}
|
||||
testValue := &TestValue{
|
||||
Name: "hi",
|
||||
}
|
||||
|
||||
testStore := storeProvider.New("test", testValue)
|
||||
|
||||
err = testStore.Set(testValue)
|
||||
is.NoErr(err)
|
||||
testStore.resync()
|
||||
value := testStore.Get()
|
||||
is.Equal(value, testValue)
|
||||
is.Equal(value.(*TestValue).Name, "hi")
|
||||
|
||||
testValue = &TestValue{
|
||||
Name: "there",
|
||||
}
|
||||
err = testStore.Set(testValue)
|
||||
is.NoErr(err)
|
||||
testStore.resync()
|
||||
value = testStore.Get()
|
||||
is.Equal(value, testValue)
|
||||
is.Equal(value.(*TestValue).Name, "there")
|
||||
|
||||
}
|
||||
|
||||
func TestStoreProvider_RapidReadWrite(t *testing.T) {
|
||||
is := is2.New(t)
|
||||
|
||||
defaultLogger := logger.NewDefaultLogger()
|
||||
testLogger := internallogger.New(defaultLogger)
|
||||
//testLogger.SetLogLevel(logger.TRACE)
|
||||
serviceBus := servicebus.New(testLogger)
|
||||
err := serviceBus.Start()
|
||||
is.NoErr(err)
|
||||
defer serviceBus.Stop()
|
||||
|
||||
testRuntime := New(serviceBus)
|
||||
storeProvider := newStore(testRuntime)
|
||||
|
||||
testStore := storeProvider.New("test", 1)
|
||||
|
||||
ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
readers := 100
|
||||
writers := 100
|
||||
wg.Add(readers + writers)
|
||||
// Setup readers
|
||||
go func(testStore *Store, ctx context.Context) {
|
||||
for readerCount := 0; readerCount < readers; readerCount++ {
|
||||
go func(store *Store, ctx context.Context, id int) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
wg.Done()
|
||||
return
|
||||
default:
|
||||
store.Get()
|
||||
}
|
||||
}
|
||||
}(testStore, ctx, readerCount)
|
||||
}
|
||||
}(testStore, ctx)
|
||||
|
||||
// Setup writers
|
||||
go func(testStore *Store, ctx context.Context) {
|
||||
for writerCount := 0; writerCount < writers; writerCount++ {
|
||||
go func(store *Store, ctx context.Context, id int) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
wg.Done()
|
||||
return
|
||||
default:
|
||||
err := store.Set(rand.Int())
|
||||
is.NoErr(err)
|
||||
}
|
||||
}
|
||||
}(testStore, ctx, writerCount)
|
||||
}
|
||||
}(testStore, ctx)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -18,8 +18,6 @@ type Window interface {
|
||||
Unminimise()
|
||||
SetTitle(title string)
|
||||
SetSize(width int, height int)
|
||||
SetMinSize(width int, height int)
|
||||
SetMaxSize(width int, height int)
|
||||
SetPosition(x int, y int)
|
||||
Fullscreen()
|
||||
UnFullscreen()
|
||||
@@ -87,18 +85,6 @@ func (w *window) SetSize(width int, height int) {
|
||||
w.bus.Publish(message, "")
|
||||
}
|
||||
|
||||
// SetSize sets the size of the window
|
||||
func (w *window) SetMinSize(width int, height int) {
|
||||
message := fmt.Sprintf("window:minsize:%d:%d", width, height)
|
||||
w.bus.Publish(message, "")
|
||||
}
|
||||
|
||||
// SetSize sets the size of the window
|
||||
func (w *window) SetMaxSize(width int, height int) {
|
||||
message := fmt.Sprintf("window:maxsize:%d:%d", width, height)
|
||||
w.bus.Publish(message, "")
|
||||
}
|
||||
|
||||
// SetPosition sets the position of the window
|
||||
func (w *window) SetPosition(x int, y int) {
|
||||
message := fmt.Sprintf("window:position:%d:%d", x, y)
|
||||
|
||||
@@ -71,11 +71,10 @@ func (s *ServiceBus) Start() error {
|
||||
}
|
||||
|
||||
// We run in a different thread
|
||||
s.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
|
||||
quit := false
|
||||
s.wg.Add(1)
|
||||
|
||||
// Loop until we get a quit message
|
||||
for !quit {
|
||||
|
||||
@@ -80,9 +80,7 @@ func (m *Menu) Start() error {
|
||||
|
||||
type ClickCallbackMessage struct {
|
||||
MenuItemID string `json:"menuItemID"`
|
||||
MenuType string `json:"menuType"`
|
||||
Data string `json:"data"`
|
||||
ParentID string `json:"parentID"`
|
||||
}
|
||||
|
||||
var callbackData ClickCallbackMessage
|
||||
@@ -93,7 +91,7 @@ func (m *Menu) Start() error {
|
||||
return
|
||||
}
|
||||
|
||||
err = m.menuManager.ProcessClick(callbackData.MenuItemID, callbackData.Data, callbackData.MenuType, callbackData.ParentID)
|
||||
err = m.menuManager.ProcessClick(callbackData.MenuItemID, callbackData.Data)
|
||||
if err != nil {
|
||||
m.logger.Trace("%s", err.Error())
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func (r *Runtime) Start() error {
|
||||
if r.startupCallback != nil {
|
||||
go r.startupCallback(r.runtime)
|
||||
} else {
|
||||
r.logger.Warning("no startup callback registered!")
|
||||
r.logger.Error("no startup callback registered!")
|
||||
}
|
||||
default:
|
||||
r.logger.Error("unknown hook message: %+v", hooksMessage)
|
||||
@@ -131,8 +131,6 @@ func (r *Runtime) GoRuntime() *runtime.Runtime {
|
||||
func (r *Runtime) shutdown() {
|
||||
if r.shutdownCallback != nil {
|
||||
go r.shutdownCallback()
|
||||
} else {
|
||||
r.logger.Warning("no shutdown callback registered!")
|
||||
}
|
||||
r.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
@@ -2,21 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Create application with options
|
||||
app, err := wails.CreateApp("{{.ProjectName}}", 1024, 768)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
app := wails.CreateApp("{{.ProjectName}}", 1024, 768)
|
||||
|
||||
app.Bind(newBasic())
|
||||
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
app.Run()
|
||||
}
|
||||
|
||||
@@ -20,14 +20,6 @@ type WebClient struct {
|
||||
running bool
|
||||
}
|
||||
|
||||
func (wc *WebClient) SetTrayMenu(trayMenuJSON string) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
func (wc *WebClient) UpdateTrayMenuLabel(trayMenuJSON string) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
func (wc *WebClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
|
||||
wc.logger.Info("Not implemented in server build")
|
||||
}
|
||||
|
||||
@@ -211,6 +211,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
options.CompiledBinary = compiledBinary
|
||||
|
||||
// Create the command
|
||||
fmt.Printf("Compile command: %+v", commands.AsSlice())
|
||||
cmd := exec.Command(options.Compiler, commands.AsSlice()...)
|
||||
|
||||
// Set the directory
|
||||
|
||||
@@ -104,7 +104,7 @@ func Build(options *Options) (string, error) {
|
||||
// return "", err
|
||||
// }
|
||||
if !options.IgnoreFrontend {
|
||||
outputLogger.Println(" - Building Project Frontend")
|
||||
outputLogger.Println(" - Building Wails Frontend")
|
||||
err = builder.BuildFrontend(outputLogger)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
type MenuItem struct {
|
||||
// Label is what appears as the menu text
|
||||
Label string
|
||||
// AlternateLabel is a secondary label (Used by Mac)
|
||||
AlternateLabel string
|
||||
// Role is a predefined menu type
|
||||
Role Role `json:"Role,omitempty"`
|
||||
// Accelerator holds a representation of a key binding
|
||||
@@ -21,6 +23,14 @@ type MenuItem struct {
|
||||
Hidden bool
|
||||
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
|
||||
Checked bool
|
||||
// Font to use for the menu item
|
||||
Font string
|
||||
// Font to use for the menu item
|
||||
FontSize int
|
||||
// RGBA is the colour of the menu item
|
||||
RGBA string
|
||||
// Image is an image for the menu item (base64 string)
|
||||
Image string
|
||||
// Submenu contains a list of menu items that will be shown as a submenu
|
||||
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
|
||||
SubMenu *Menu `json:"SubMenu,omitempty"`
|
||||
@@ -28,18 +38,11 @@ type MenuItem struct {
|
||||
// Callback function when menu clicked
|
||||
Click Callback `json:"-"`
|
||||
|
||||
// Colour
|
||||
RGBA string
|
||||
// Foreground colour in hex RGBA format EG: 0xFF0000FF = #FF0000FF = red
|
||||
Foreground int
|
||||
|
||||
// Font
|
||||
FontSize int
|
||||
FontName string
|
||||
|
||||
// Image - base64 image data
|
||||
Image string
|
||||
|
||||
// Tooltip
|
||||
Tooltip string
|
||||
// Background colour
|
||||
Background int
|
||||
|
||||
// This holds the menu item's parent.
|
||||
parent *MenuItem
|
||||
|
||||
@@ -5,13 +5,13 @@ type Type string
|
||||
|
||||
const (
|
||||
// TextType is the text menuitem type
|
||||
TextType Type = "Text"
|
||||
TextType Type = "t"
|
||||
// SeparatorType is the Separator menuitem type
|
||||
SeparatorType Type = "Separator"
|
||||
SeparatorType Type = "s"
|
||||
// SubmenuType is the Submenu menuitem type
|
||||
SubmenuType Type = "Submenu"
|
||||
SubmenuType Type = "S"
|
||||
// CheckboxType is the Checkbox menuitem type
|
||||
CheckboxType Type = "Checkbox"
|
||||
CheckboxType Type = "c"
|
||||
// RadioType is the Radio menuitem type
|
||||
RadioType Type = "Radio"
|
||||
RadioType Type = "r"
|
||||
)
|
||||
|
||||
@@ -13,10 +13,8 @@ var Default = &App{
|
||||
DevTools: false,
|
||||
RGBA: 0xFFFFFFFF,
|
||||
Mac: &mac.Options{
|
||||
TitleBar: mac.TitleBarDefault(),
|
||||
Appearance: mac.DefaultAppearance,
|
||||
WebviewIsTransparent: false,
|
||||
WindowBackgroundIsTranslucent: false,
|
||||
TitleBar: mac.TitleBarDefault(),
|
||||
Appearance: mac.DefaultAppearance,
|
||||
},
|
||||
Logger: logger.NewDefaultLogger(),
|
||||
LogLevel: logger.INFO,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
wailsruntime "github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"log"
|
||||
"runtime"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
@@ -34,7 +33,6 @@ type App struct {
|
||||
LogLevel logger.LogLevel
|
||||
Startup func(*wailsruntime.Runtime) `json:"-"`
|
||||
Shutdown func() `json:"-"`
|
||||
Bind []interface{}
|
||||
}
|
||||
|
||||
// MergeDefaults will set the minimum default values for an application
|
||||
|
||||
19
v2/wails.go
19
v2/wails.go
@@ -14,12 +14,19 @@ type Runtime = runtime.Runtime
|
||||
// Store is an alias for the Store object
|
||||
type Store = runtime.Store
|
||||
|
||||
// Run creates an application based on the given config and executes it
|
||||
func Run(options *options.App) error {
|
||||
app, err := app.CreateApp(options)
|
||||
if err != nil {
|
||||
return err
|
||||
// CreateAppWithOptions creates an application based on the given config
|
||||
func CreateAppWithOptions(options *options.App) (*app.App, error) {
|
||||
return app.CreateApp(options)
|
||||
}
|
||||
|
||||
// CreateApp creates an application based on the given title, width and height
|
||||
func CreateApp(title string, width int, height int) (*app.App, error) {
|
||||
|
||||
options := &options.App{
|
||||
Title: title,
|
||||
Width: width,
|
||||
Height: height,
|
||||
}
|
||||
|
||||
return app.Run()
|
||||
return app.CreateApp(options)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user