Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add adw.SpinRow widget #88

Open
can-lehmann opened this issue Sep 17, 2023 · 9 comments
Open

Add adw.SpinRow widget #88

can-lehmann opened this issue Sep 17, 2023 · 9 comments
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Comments

@can-lehmann
Copy link
Owner

https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SpinRow.html

@can-lehmann can-lehmann added enhancement New feature or request help wanted Extra attention is needed good first issue Good for newcomers labels Sep 17, 2023
PhilippMDoerner added a commit to PhilippMDoerner/owlkettle that referenced this issue Oct 3, 2023
PhilippMDoerner added a commit to PhilippMDoerner/owlkettle that referenced this issue Oct 3, 2023
@PhilippMDoerner
Copy link
Contributor

PhilippMDoerner commented Oct 3, 2023

Started tinkering around with this one.
I'm getting Segfaults when the input is handled.
That is explicitly within inputEventCallback due to nil access when casting it to SpinRowState.

  renderable SpinRow of ActionRow:
    climbRate: float = 0.0
    digits: uint = 0
    numeric: bool = false
    min: float = 0.0
    max: float = 100.0
    snapToTicks: bool = false
    updatePolicy: SpinButtonUpdatePolicy = SpinButtonUpdateAlways
    value: float = 0.0
    wrap: bool = false
    
    proc input(newValue: float)
    
    hooks:
      beforeBuild:
        let climbRate: float = if widget.hasClimbRate: widget.valClimbRate else: 0.0
        let digits: uint = if widget.hasDigits: widget.valDigits else: 0.uint
        state.internalWidget = adw_spin_row_new(GtkAdjustment(nil), climbRate.cdouble, digits.cuint)

      connectEvents:
        proc inputEventCallback(
          widget: GtkWidget,
          newValue: cdouble,
          data: ptr EventObj[proc(newValue: float)]
        ): cint {.cdecl.} =
          let x = data[].widget
          echo data == nil # false
          echo data[].widget == nil # false
          echo SpinRowState(data[].widget) == nil ## Line 740, this line segfaults according to the stacktrace
          SpinRowState(data[].widget).value = newValue.float
          data[].callback(newValue)
          data[].redraw()
          
          result = true.cint
        
        state.connect(state.input, "input", inputEventCallback)

  ... tons of property hooks that all seem to work fine even though I don't quite get what some of the properties do...

stacktrace:

false
false
Traceback (most recent call last)
/home/philipp/dev/owlkettle/examples/widgets/adw/spin_row.nim(63) spin_row
/home/philipp/dev/owlkettle/owlkettle/adw.nim(912) brew
/home/philipp/dev/owlkettle/owlkettle/mainloop.nim(96) runMainloop
/home/philipp/dev/owlkettle/owlkettle/adw.nim(740) inputEventCallback
/home/philipp/.choosenim/toolchains/nim-2.0.0/lib/system/arc.nim(238) isObjDisplayCheck
SIGSEGV: Illegal storage access. (Attempt to read from nil?)

I don't quite see why this blows up on me.
I'm trying to cross reference with stuff from my Scale Example and Spinbutton but nothing really jumps out to me.

Honestly I don't even quite get the GTK Explanation of what that signal is good for:

Emitted to convert the user’s input into a double value.

Like what? The main reason I wrap this because I assume this is what gets called if you change the value in general on this widget.

Any idea what I could look into to try and solve this? I'm not even sure where to look next as the fact that it only blows up inside of inputEventCallback tells me that inputEventCallback itself is perfectly fine, just somehow the casting to SpinRowState kills me despite the widget on data not being nil.

I'm a bit flummoxed.

(Edit: On an unrelated sidenote: That PR to be able to deal with different adwaita versions paid of so fast it made my head spin. Literally merged the PR and the next issue I stare at requires me to check for Adwaita version 1.4 😆 )

@can-lehmann
Copy link
Owner Author

One thing I noticed is that your signal handler takes a newValue: cdouble, but GTK passes gdouble*.

@can-lehmann
Copy link
Owner Author

The idea behind input is that you can use a custom parsing function. (idk, maybe parse inputs with units like 10cm to 0.1m)

@PhilippMDoerner
Copy link
Contributor

One thing I noticed is that your signal handler takes a newValue: cdouble, but GTK passes gdouble*.

is that not the same? Wait does that star make this a ptr cdouble?

@PhilippMDoerner
Copy link
Contributor

I officially do not know where the actual value I type into the field comes from or is supposed to come from.
It's not newValue because even unref'd whatever is in there is nowhere close whatever I type into the spinButton.
It's neither adw_spin_row_get_value(widget).float because somehow that always returns 0.

If I type in 90 into that value field, it'll do a lot but not return me 90 anywhere.
There's also some really weird behaviour in that it sometimes defaults back to 0 and sometimes it sticks with the value, which I assume is because the event-callback isn't getting called because it's somehow bricked for a reason I can't discern.

@PhilippMDoerner
Copy link
Contributor

PhilippMDoerner commented Oct 4, 2023

Hand --> Face

Seriously? The spinbutton wants you to use gtk_editable_get_text to fetch the userinput as text. Okay I tend to just not understand gtk sometimes, this should do it.
I know that's what was written directly in the docs, it just seemed so out of pocket that I assumed it was for something else entirely and discarded that sentence.

PhilippMDoerner added a commit to PhilippMDoerner/owlkettle that referenced this issue Oct 5, 2023
PhilippMDoerner added a commit to PhilippMDoerner/owlkettle that referenced this issue Oct 5, 2023
@PhilippMDoerner
Copy link
Contributor

Yeah I'm as stuck as I was yesterday. I updated the example on the branch a bit as well as the widget, but basically I can't get this to work properly.

Any value you input does not stick.
A change to the value for some reason does not trigger the property hook that would update the gtk widget.
Even if you update the gtk-widget inside of the custom eventCallback the value doesn't stick.
If you update a value 3-5 times it somehow crashes the widget because suddenly SpinRowState(data[].widget) becomes an invalid object conversion.
Updating the newValueHolder: ptr cdouble parameter doesn't do anything anywhere that I can see.

I'm pretty much stuck.
I guess the nice thing is that either way I already did most of the typing work ?

Below the code on the widget to provide cursory glances:

when AdwVersion >= (1, 4):
  renderable SpinRow of ActionRow:
    climbRate: float = 0.0
    digits: uint = 0
    numeric: bool = false
    min: float = 0.0
    max: float = 100.0
    snapToTicks: bool = false
    updatePolicy: SpinButtonUpdatePolicy = SpinButtonUpdateAlways
    value: float = 0.0
    wrap: bool = false
    parseToFloat: (proc(input: string): float) = parseFloat
    
    proc input(newValue: float)
    
    hooks:
      beforeBuild:
        let climbRate: float = if widget.hasClimbRate: widget.valClimbRate else: 0.0
        let digits: uint = if widget.hasDigits: widget.valDigits else: 0.uint
        state.internalWidget = adw_spin_row_new(GtkAdjustment(nil), climbRate.cdouble, digits.cuint)

      connectEvents:
        proc inputEventCallback(
          widget: GtkWidget,
          newValueHolder: ptr cdouble,
          data: ptr EventObj[proc(newValue: float)]
        ): cint {.cdecl.} =          
          var newValue: float
          try:
            newValue = SpinRowState(data[].widget).parseToFloat($gtk_editable_get_text(widget))
          except ValueError as e:
            return GtkInputError
            
          echo "Input event callback: ", newValue
          echo "\n"
          
          newValueHolder[] = newValue.cdouble
          SpinRowState(data[].widget).value = newValue
          data[].callback(newValue)
          data[].redraw()
          
          return true.cint
        
        state.connect(state.input, "input", inputEventCallback) 
        # state.connect(state.output, "output", eventCallback)
        # state.connect(state.wrapped, "wrapped", eventCallback)
        
    hooks climbRate:
      property:
        adw_spin_row_set_climb_rate(state.internalWidget, state.climbRate.cdouble)
    
    hooks digits:
      property:
        adw_spin_row_set_digits(state.internalWidget, state.digits.cuint)
    
    hooks numeric:
      property:
        adw_spin_row_set_numeric(state.internalWidget, state.numeric.cbool)
        
    hooks min:
      property:
        adw_spin_row_set_range(state.internalWidget, state.min.cdouble, state.max.cdouble)

    hooks max:
      property:
        adw_spin_row_set_range(state.internalWidget, state.min.cdouble, state.max.cdouble)
    
    hooks snapToTicks:
      property:
        adw_spin_row_set_snap_to_ticks(state.internalWidget, state.snapToTicks.cbool)
        
    hooks updatePolicy:
      property:
        adw_spin_row_set_update_policy(state.internalWidget, state.updatePolicy)
        
    hooks value:
      property:  
        echo "Value was updated to: ", state.value
        adw_spin_row_set_value(state.internalWidget, state.value.cdouble)
        
    hooks wrap:
      property:
        adw_spin_row_set_wrap(state.internalWidget, state.wrap.cbool)

  export SpinRow

@can-lehmann
Copy link
Owner Author

A change to the value for some reason does not trigger the property hook that would update the gtk widget.

You currently don't have a changed/valueChanged/... callback (not sure what it is called for SpinRow), that makes a 2-way binding possible. input should only be used for parsing as far as I know.

PhilippMDoerner added a commit to PhilippMDoerner/owlkettle that referenced this issue Oct 7, 2023
PhilippMDoerner added a commit to PhilippMDoerner/owlkettle that referenced this issue Oct 7, 2023
@PhilippMDoerner
Copy link
Contributor

A change to the value for some reason does not trigger the property hook that would update the gtk widget.

You currently don't have a changed/valueChanged/... callback (not sure what it is called for SpinRow), that makes a 2-way binding possible. input should only be used for parsing as far as I know.

Tried that just now by trying to start off simple and only doing changed.
You still have to fetch the value via parseFloat($gtk_editable_get_text(widget)) because proc adw_spin_row_get_value(self: GtkWidget): cdouble nets you the old value 100% of the time after.

So with this approach, I have a widget. However that widget has the following bugs:

  • Entering a value larger than max is somehow allowed and is not immediately limited to max
  • If you enter invalid values a couple times (to which 0 counts for some reason) you get ObjectConversionDefects and those occuring repeatedly it causes a segfault at these lines: SpinRowState(data[].widget).value = newValue because somehow data[].widget turns into nil (?)

Here the ObjectConversionStackTrace echo'd out:

ref Exception(parent: nil, name: "ObjectConversionDefect", msg: "invalid object conversion", trace: 
@[
StackTraceEntry(procname: "spin_row", line: 61, filename: "/home/philipp/dev/owlkettle/examples/widgets/adw/spin_row.nim"), 
StackTraceEntry(procname: "brew", line: 920, filename: "/home/philipp/dev/owlkettle/owlkettle/adw.nim"), 
StackTraceEntry(procname: "runMainloop", line: 96, filename: "/home/philipp/dev/owlkettle/owlkettle/mainloop.nim"), 
StackTraceEntry(procname: "changedCallback", line: 748, filename: "/home/philipp/dev/owlkettle/owlkettle/adw.nim"), 
StackTraceEntry(procname: "sysFatal", line: 53, filename: "/home/philipp/.choosenim/toolchains/nim-2.0.0/lib/system/fatal.nim")
], 
up: nil)

I just can't explain why 0 somehow is a value that leads to ObjectConversionDefects. Nor why catching those exception still enables Segfaults elsewhere.

Below the current still completely busted approach:

when AdwVersion >= (1, 4):
  renderable SpinRow of ActionRow:
    climbRate: float = 0.0
    digits: uint = 0
    numeric: bool = false
    min: float = 0.0
    max: float = 100.0
    snapToTicks: bool = false
    updatePolicy: SpinButtonUpdatePolicy = SpinButtonUpdateAlways
    value: float = 0
    wrap: bool = false
    
    proc changed(newValue: float)
    
    hooks:
      beforeBuild:
        let climbRate: float = if widget.hasClimbRate: widget.valClimbRate else: 0.0
        let digits: uint = if widget.hasDigits: widget.valDigits else: 0.uint
        state.internalWidget = adw_spin_row_new(GtkAdjustment(nil), climbRate.cdouble, digits.cuint)

      connectEvents:        
        proc changedCallback(widget: GtkWidget, data: ptr EventObj[proc (newValue: float)]) {.cdecl.} =
          echo "Changed Signal"
          var newValue: float
          try:
            let valueString = $gtk_editable_get_text(widget)
            echo "The value: ", valueString
            newValue = parseFloat(valueString)
          except Exception as e:
            echo "Borked: ", e.repr
            newValue = 0.0
          
          echo data.isNil
          echo data[].widget.isNil
          SpinRowState(data[].widget).value = newValue
            
          data[].callback(newValue)
          data[].redraw()
          
        state.connect(state.changed, "changed", changedCallback)
        
        # state.connect(state.output, "output", eventCallback)
        # state.connect(state.wrapped, "wrapped", eventCallback)
        
    hooks climbRate:
      property:
        adw_spin_row_set_climb_rate(state.internalWidget, state.climbRate.cdouble)
    
    hooks digits:
      property:
        adw_spin_row_set_digits(state.internalWidget, state.digits.cuint)
    
    hooks numeric:
      property:
        adw_spin_row_set_numeric(state.internalWidget, state.numeric.cbool)
        
    hooks min:
      property:
        adw_spin_row_set_range(state.internalWidget, state.min.cdouble, state.max.cdouble)

    hooks max:
      property:
        adw_spin_row_set_range(state.internalWidget, state.min.cdouble, state.max.cdouble)
    
    hooks snapToTicks:
      property:
        adw_spin_row_set_snap_to_ticks(state.internalWidget, state.snapToTicks.cbool)
        
    hooks updatePolicy:
      property:
        adw_spin_row_set_update_policy(state.internalWidget, state.updatePolicy)
        
    hooks value:
      property:  
        echo "Value was updated to: ", state.value
        adw_spin_row_set_value(state.internalWidget, state.value.cdouble)
        
    hooks wrap:
      property:
        adw_spin_row_set_wrap(state.internalWidget, state.wrap.cbool)

  export SpinRow

PhilippMDoerner added a commit to PhilippMDoerner/owlkettle that referenced this issue Oct 10, 2023
PhilippMDoerner added a commit to PhilippMDoerner/owlkettle that referenced this issue Oct 10, 2023
This doesn't really make it all that batter, it still segfaults
if you do anything, but at least this is more according to the docs.
No idea how to fix it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants