ui/cocoa/entry.m

Sat, 04 Oct 2025 14:52:59 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 04 Oct 2025 14:52:59 +0200
changeset 110
c00e968d018b
permissions
-rw-r--r--

fix repolist menu button

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2025 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#import "entry.h"
#import <objc/runtime.h>

@implementation UiSpinBox

- (UiSpinBox*)init {
    return self;
}

- (void)valueChanged {
    float value = _stepper.doubleValue;
    UiEvent e;
    e.obj = _obj;
    e.window = e.obj->window;
    e.document = e.obj->ctx->document;
    e.eventdata = NULL;
    e.eventdatatype = 0;
    e.intval = (int)value;
    e.set = ui_get_setop();
    
    if(_onchange) {
        _onchange(&e, _onchangedata);
    }
    
    if(_observers) {
        UiObserver *observer = *_observers;
        ui_notify_evt(observer, &e);
    }
}

- (void)stepperChanged:(id)sender {
    if(_isInteger) {
        _textfield.integerValue = _stepper.integerValue;
    } else {
        _textfield.doubleValue = _stepper.doubleValue;
    }
    [self valueChanged];
}

- (void) controlTextDidChange:(NSNotification *)obj {
    if(_isInteger) {
        _stepper.integerValue = _textfield.integerValue;
    } else {
        _stepper.doubleValue = _textfield.doubleValue;
    }
    [self valueChanged];
}

@end

UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) {
    double min = args->min;
    double max = args->max != 0 ? args->max : 1000;
    
    UiVar *var = NULL;
    UiVarType vartype = 0;
    if(args->varname) {
        var = uic_get_var(obj->ctx, args->varname);
        if(var) {
            vartype = var->type;
        } else {
            var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, args->varname, UI_VAR_RANGE);
            vartype = UI_VAR_RANGE;
        }
    }
    
    if(!var) {
        if(args->intvalue) {
            var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER);
            vartype = UI_VAR_INTEGER;
        } else if(args->doublevalue) {
            var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE);
            vartype = UI_VAR_DOUBLE;
        } else if(args->rangevalue) {
            var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE);
            vartype = UI_VAR_RANGE;
        }
    }
    
    if(vartype == UI_VAR_RANGE) {
        UiRange *r = var->value;
        min = r->min;
        max = r->max;
    }
    if(args->step == 0) {
        args->step = 1;
    }
    
    // create and setup textfield for number input
    NSTextField *textfield = [[NSTextField alloc] init];
    textfield.translatesAutoresizingMaskIntoConstraints = NO;
    
    if(!args->hfill || args->width > 0) {
        textfield.translatesAutoresizingMaskIntoConstraints = NO;
        int width = args->width > 0 ? args->width : 100;
        [[textfield.widthAnchor constraintEqualToConstant:width] setActive:YES];
    }
    
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    formatter.numberStyle = NSNumberFormatterDecimalStyle;
    formatter.allowsFloats = vartype != UI_VAR_INTEGER;
    formatter.minimumFractionDigits = args->digits;
    formatter.maximumFractionDigits = args->digits;

    textfield.formatter = formatter;

    // create view containing the textfield and stepper
    NSView *view = [[NSView alloc]init];
    view.translatesAutoresizingMaskIntoConstraints = NO;
    
    NSStepper *stepper = [[NSStepper alloc] init];
    stepper.translatesAutoresizingMaskIntoConstraints = NO;
    
    [view addSubview:textfield];
    [view addSubview:stepper];

    [NSLayoutConstraint activateConstraints:@[
        [textfield.leadingAnchor constraintEqualToAnchor:view.leadingAnchor],
        [textfield.topAnchor constraintEqualToAnchor:view.topAnchor],
        [textfield.bottomAnchor constraintEqualToAnchor:view.bottomAnchor],
        
        [stepper.trailingAnchor constraintEqualToAnchor:view.trailingAnchor],
        [stepper.topAnchor constraintEqualToAnchor:view.topAnchor],
        [stepper.bottomAnchor constraintEqualToAnchor:view.bottomAnchor],
        
        [textfield.trailingAnchor constraintEqualToAnchor:stepper.leadingAnchor]
    ]];
    
    UiLayout layout = UI_INIT_LAYOUT(args);
    ui_container_add(obj, view, &layout);
    
    // create the spinbox object, that handles all textfield and stepper events
    UiSpinBox *spinbox = [[UiSpinBox alloc]init];
    spinbox.obj = obj;
    spinbox.textfield = textfield;
    spinbox.stepper = stepper;
    spinbox.onchange = args->onchange;
    spinbox.onchangedata = args->onchangedata;
    spinbox.isInteger = vartype == UI_VAR_INTEGER;
    objc_setAssociatedObject(stepper, "ui_spinbox", spinbox, OBJC_ASSOCIATION_RETAIN);
    
    stepper.minValue = min;
    stepper.maxValue = max;
    stepper.increment = args->step;
    stepper.target = spinbox;
    stepper.action = @selector(stepperChanged:);
    textfield.delegate = spinbox;
    
    UiObserver **obs = NULL;
    if(var) {
        void *varObj = (__bridge void*)spinbox;
        switch(vartype) {
            default: break;
            case UI_VAR_INTEGER: {
                UiInteger *i = var->value;
                i->get = ui_spinbutton_getint;
                i->set = ui_spinbutton_setint;
                i->obj = varObj;
                obs = &i->observers;
                
                stepper.integerValue = i->value;
                textfield.integerValue = i->value;
                break;
            }
            case UI_VAR_DOUBLE: {
                UiDouble *d = var->value;
                d->get = ui_spinbutton_getdouble;
                d->set = ui_spinbutton_setdouble;
                d->obj = varObj;
                obs = &d->observers;
                
                stepper.doubleValue = d->value;
                textfield.doubleValue = d->value;
                break;
            }
            case UI_VAR_RANGE: {
                UiRange *r = var->value;
                r->get = ui_spinbutton_getrangeval;
                r->set = ui_spinbutton_setrangeval;
                r->setrange = ui_spinbutton_setrange;
                r->setextent = ui_spinbutton_setextent;
                r->obj = varObj;
                obs = &r->observers;
                
                stepper.doubleValue = r->value;
                textfield.doubleValue = r->value;
                break;
            }
        }
    }
    spinbox.observers = obs;
    
    return (__bridge void*)textfield;
}

int64_t ui_spinbutton_getint(UiInteger *i) {
    UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj;
    i->value = spinbox.stepper.integerValue;
    return i->value;
}

void ui_spinbutton_setint(UiInteger *i, int64_t val) {
    UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj;
    i->value = val;
    spinbox.stepper.integerValue = val;
    spinbox.textfield.integerValue = val;
}

double ui_spinbutton_getdouble(UiDouble *d) {
    UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj;
    d->value = spinbox.stepper.doubleValue;
    return d->value;
}

void ui_spinbutton_setdouble(UiDouble *d, double val) {
    UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj;
    d->value = val;
    spinbox.stepper.doubleValue = val;
    spinbox.textfield.doubleValue = val;
}

double ui_spinbutton_getrangeval(UiRange *r) {
    UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj;
    r->value = spinbox.stepper.doubleValue;
    return r->value;
}

void ui_spinbutton_setrangeval(UiRange *r, double val) {
    UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj;
    r->value = val;
    spinbox.stepper.doubleValue = val;
    spinbox.textfield.doubleValue = val;
}

void ui_spinbutton_setrange(UiRange *r, double min, double max) {
    UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj;
    spinbox.stepper.minValue = min;
    spinbox.stepper.maxValue = max;
}

void ui_spinbutton_setextent(UiRange *r, double extent) {
    UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj;
    spinbox.stepper.increment = extent;
}

mercurial