Changing a SpinGadget value by mouse wheel

Got an idea for enhancing PureBasic? New command(s) you'd like to see?
uwekel
Enthusiast
Enthusiast
Posts: 740
Joined: Sat Dec 03, 2011 5:54 pm
Location: Oldenburg (Germany)

Changing a SpinGadget value by mouse wheel

Post by uwekel »

Hi,

Normally, on Windows (NumericSpinButton) and Linux (GtkSpinButton), you can edit a value by using the mouse wheel.

Not so with the PB SpinGaget, what would be great.

Best regards
Uwe
PB 5.70 LTS (x64) - Debian Testing, Gnome 3.30.2
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Re: Changing a SpinGadget value by mouse wheel

Post by PB »

Mousewheel works fine here on Win 7 64-bit with a SpinGadget.
Tested with the example in the manual after giving it the focus.
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Changing a SpinGadget value by mouse wheel

Post by Shardik »

PB wrote:Mousewheel works fine here on Win 7 64-bit with a SpinGadget.
Tested with the example in the manual after giving it the focus.
Mousewheel works also fine in Windows XP x86 SP3 and Windows 7 x86 SP1.

In Linux you may use the GtkSpinButton which supports changing values by using the mouse wheel. Simply position the cursor above the spin button and you are able to change the spin button values with your mouse wheel (tested with PB 5.11 x86 on Kubuntu 12.04 x86):

Code: Select all

ImportC ""
  gtk_spin_button_new_with_range(Minimum.D, Maximum.D, StepIncrement.D)
EndImport

OpenWindow(0, 200, 100, 270, 60, "GtkSpinButton")
FixedBox = g_list_nth_data_(gtk_container_get_children_(gtk_bin_get_child_(WindowID(0))), 0)
SpinButton = gtk_spin_button_new_with_range(1, 10, 1)
gtk_fixed_put_(FixedBox, SpinButton, 100, 20)
gtk_widget_show_all_(WindowID(0))

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
uwekel
Enthusiast
Enthusiast
Posts: 740
Joined: Sat Dec 03, 2011 5:54 pm
Location: Oldenburg (Germany)

Re: Changing a SpinGadget value by mouse wheel

Post by uwekel »

Thank you Shardik for the code sample! It looks promising.
PB 5.70 LTS (x64) - Debian Testing, Gnome 3.30.2
uwekel
Enthusiast
Enthusiast
Posts: 740
Joined: Sat Dec 03, 2011 5:54 pm
Location: Oldenburg (Germany)

Re: Changing a SpinGadget value by mouse wheel

Post by uwekel »

PB wrote:Mousewheel works fine here on Win 7 64-bit with a SpinGadget.
So in terms of multi-platform development i would like to declare it as a BUG for the linux version. Do i have to write another article in the "Linux - Bugs" section or can this one be moved?
PB 5.70 LTS (x64) - Debian Testing, Gnome 3.30.2
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Changing a SpinGadget value by mouse wheel

Post by Shardik »

I have modified my example from above to work without any change in both Gtk2 and Gtk3 in PB 5.3x and PB 5.4x in both ASCII and Unicode mode. Unfortunately the look and design of the GtkSpinButton was changed from GTK2 to GTK3:
Image
Image
PureBasic is using a custom Gadget (a GtkHBox containing other Gadgets as stated in PB's blog) for its SpinGadget thus resulting in the same look in GTK2 and GTK3.

Code: Select all

EnableExplicit

ImportC ""
  gtk_spin_button_new_with_range(Minimum.D, Maximum.D, StepIncrement.D)
EndImport

Procedure.I GetFixedBox(WindowID.I)
  Protected Child.I
  Protected ChildrenList.I
  Protected *Name
  Protected Widget.I = WindowID(WindowID)

  Child = gtk_bin_get_child_(Widget)

  Repeat
    ChildrenList = gtk_container_get_children_(Widget)
    Child = g_list_nth_data_(ChildrenList, 0)

    If Child = 0
      Break
    Else
      *Name = gtk_widget_get_name_(Child)
      Widget = Child
    EndIf
  Until PeekS(*Name, -1, #PB_UTF8) = "GtkFixed"

  ProcedureReturn Child
EndProcedure

Define FixedBox.I
Define SpinButton.I
Define Subsystem.S
Define x.I

CompilerIf #PB_Compiler_Version > 531
  CompilerIf Subsystem("gtk2")
    Subsystem = "Gtk2"
    x = 100
  CompilerElse
    Subsystem = "Gtk3"
    x = 80
  CompilerEndIf
CompilerElse
  CompilerIf Subsystem("gtk3")
    Subsystem = "Gtk3"
    x = 80
  CompilerElse
    Subsystem = "Gtk2"
    x = 100
  CompilerEndIf
CompilerEndIf

OpenWindow(0, 200, 100, 270, 60, "GtkSpinButton - " + Subsystem)
FixedBox = GetFixedBox(0)
SpinButton = gtk_spin_button_new_with_range(1, 100, 1)
gtk_fixed_put_(FixedBox, SpinButton, x, 20)
gtk_widget_show_all_(WindowID(0))

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: Changing a SpinGadget value by mouse wheel

Post by Shardik »

For those who prefer PB's SpinGadget to the API GtkSpinButton I have found a workaround for Gtk2 and Gtk3 to detect mouse wheel events in a SpinGadget and react accordingly.

Unfortunately the Gtk3 code is more complicated because of design changes from Gtk2 to Gtk3:
In Gtk3 the widgets don't possess anymore an own GdkWindow. In Gtk2 it's easy to detect whether the mouse wheel scroll event was fired by the GtkEntry widget contained in the SpinGadget by simply comparing the GdkWindow of the event with that of the GtkEntry. In Gtk3 it's necessary to compare the coordinates of the event with those of the GtkEntry and taking also into account the padding inside the GtkEntry (to work equally well with different desktop managers and themes)...

I have tested the following code examples successfully with PB 5.41 x64 in ASCII and Unicode mode on Ubuntu 14.04 x64 with these different desktop managers:
- Enlightenment E17
- KDE
- Unity

Gtk2 (subsystem "gtk2"):

Code: Select all

EnableExplicit

#SpinGadget = 0

Define *Entry.GtkWidget

Procedure.I GetChildContainer(Widget.I, ContainerName.S)
  Protected Child.I
  Protected ChildrenList.I
  Protected i.I
  Protected *Name
  
  ChildrenList = gtk_container_get_children_(Widget)
  
  If ChildrenList
    For i = 0 To g_list_length_(ChildrenList)
      Child = g_list_nth_data_(ChildrenList, i)
      
      If Child
        *Name = gtk_widget_get_name_(Child)
        
        If PeekS(*Name, -1, #PB_UTF8) = ContainerName
          Widget = Child
          Break
        EndIf
      EndIf
    Next i
  EndIf
  ProcedureReturn Child
EndProcedure

ProcedureC MouseWheelCallback(*Event.GdkEventScroll, *UserData)
  Shared *Entry.GtkWidget

  If *Event\type = #GDK_SCROLL
    If *Entry\window = gdk_window_get_parent_(*Event\window)
      Select *Event\direction
        Case #GDK_SCROLL_UP
          SetGadgetState(#SpinGadget, GetGadgetState(#SpinGadget) + 1)
        Case #GDK_SCROLL_DOWN
          SetGadgetState(#SpinGadget, GetGadgetState(#SpinGadget) - 1)
      EndSelect
    EndIf
  EndIf
  
  gtk_main_do_event_(*Event)
EndProcedure 

OpenWindow(0, 100, 100, 180, 70, "SpinGadget")
SpinGadget(#SpinGadget, 65, 20, 60, 25, 0, 100, #PB_Spin_Numeric)
SetGadgetState(#SpinGadget, 50)
SetActiveGadget(#SpinGadget)

; ----- Get GtkEntry contained in SpinGadget
*Entry = GetChildContainer(GadgetID(#SpinGadget), "GtkEntry")

; ----- Set event handler to intercept mouse wheel scroll events
gdk_event_handler_set_(@MouseWheelCallback(), 0, 0)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow

gdk_event_handler_set_(0, 0, 0)
Gtk3:

Code: Select all

EnableExplicit

#SpinGadget = 0

#GTK_STATE_FLAG_SELECTED = 1 << 2

ImportC ""
  gtk_style_context_get_padding(*Context, GtkStateFlags.I, *Border.GtkBorder)
  gtk_widget_get_style_context(*Widget.GtkWidget)
  gtk_widget_get_allocated_height(*Widget.GtkWidget)
  gtk_widget_get_allocated_width(*Widget.GtkWidget)
EndImport

Structure _GtkBorder
  left.w
  right.w
  top.w
  bottom.w
EndStructure

Define *Entry.GtkWidget
Define Padding._GtkBorder

Procedure.I GetChildContainer(Widget.I, ContainerName.S)
  Protected Child.I
  Protected ChildrenList.I
  Protected i.I
  Protected *Name
  
  ChildrenList = gtk_container_get_children_(Widget)
  
  If ChildrenList
    For i = 0 To g_list_length_(ChildrenList)
      Child = g_list_nth_data_(ChildrenList, i)
      
      If Child
        *Name = gtk_widget_get_name_(Child)
        
        If PeekS(*Name, -1, #PB_UTF8) = ContainerName
          Widget = Child
          Break
        EndIf
      EndIf
    Next i
  EndIf

  ProcedureReturn Child
EndProcedure

Procedure GetPadding(*Widget.GtkWidget)
  Shared Padding._GtkBorder

  Protected StyleContext.I

  StyleContext = gtk_widget_get_style_context(*Widget)
  gtk_style_context_get_padding(StyleContext, #GTK_STATE_FLAG_SELECTED,
    @Padding)

  Padding\top + 1
EndProcedure

ProcedureC MouseWheelCallback(*Event.GdkEventScroll, *UserData)
  Shared *Entry.GtkWidget
  Shared Padding._GtkBorder

  Protected EntryHeight.I
  Protected EntryWidth.I
  Protected Entry_x.I
  Protected Entry_y.I

  If *Event\type = #GDK_SCROLL
    Entry_x = GadgetX(#SpinGadget) + Padding\left
    Entry_y = GadgetY(#SpinGadget) + Padding\top
    EntryWidth = gtk_widget_get_allocated_width(*Entry) - (Padding\left +
      Padding\right)
    EntryHeight = gtk_widget_get_allocated_height(*Entry) - (Padding\top +
      Padding\bottom)

    If *Event\x > Entry_x And *Event\x < Entry_x + EntryWidth And
      *Event\y > Entry_y And *Event\y < Entry_y + EntryHeight
      Select *Event\direction
        Case #GDK_SCROLL_UP
          SetGadgetState(#SpinGadget, GetGadgetState(#SpinGadget) + 1)
        Case #GDK_SCROLL_DOWN
          SetGadgetState(#SpinGadget, GetGadgetState(#SpinGadget) - 1)
      EndSelect
    EndIf
  EndIf
  
  gtk_main_do_event_(*Event)
EndProcedure 

OpenWindow(0, 100, 100, 180, 70, "SpinGadget")

SpinGadget(#SpinGadget, 65, 20, 60, 25, 0, 100, #PB_Spin_Numeric)
SetActiveGadget(#SpinGadget)
SetGadgetState(#SpinGadget, 50)

; ----- Get GtkEntry contained in SpinGadget
*Entry = GetChildContainer(GadgetID(#SpinGadget), "GtkEntry")

; ----- Set event handler to intercept mouse wheel scroll events
gdk_event_handler_set_(@MouseWheelCallback(), 0, 0)

; ----- Calculate padding of GtkEntry inside of SpinGadget
GetPadding(*Entry)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow

gdk_event_handler_set_(0, 0, 0)
This is a 3rd simplified example which isn't as pixel-perfect as the Gtk3 example above (because it doesn't take internal padding into account) but which runs without any change with both Gtk3 and Gtk2 (subsystem "gtk2") and is also triggered if the cursor is above the arrows (like in Windows):

Code: Select all

EnableExplicit

#SpinGadget = 0

Procedure.I GetChildContainer(Widget.I, ContainerName.S)
  Protected Child.I
  Protected ChildrenList.I
  Protected i.I
  Protected *Name
  
  ChildrenList = gtk_container_get_children_(Widget)
  
  If ChildrenList
    For i = 0 To g_list_length_(ChildrenList)
      Child = g_list_nth_data_(ChildrenList, i)
      
      If Child
        *Name = gtk_widget_get_name_(Child)
        
        If PeekS(*Name, -1, #PB_UTF8) = ContainerName
          Widget = Child
          Break
        EndIf
      EndIf
    Next i
  EndIf

  ProcedureReturn Child
EndProcedure

ProcedureC MouseWheelCallback(*Event.GdkEventScroll, *UserData)
  If *Event\type = #GDK_SCROLL
    If WindowMouseX(0) > GadgetX(#SpinGadget) And
      WindowMouseX(0) < GadgetX(#SpinGadget) + GadgetWidth(#SpinGadget) And
      WindowMouseY(0) > GadgetY(#SpinGadget) And
      WindowMouseY(0) < GadgetY(#SpinGadget) + GadgetHeight(#SpinGadget)
      Select *Event\direction
        Case #GDK_SCROLL_UP
          SetGadgetState(#SpinGadget, GetGadgetState(#SpinGadget) + 1)
        Case #GDK_SCROLL_DOWN
          SetGadgetState(#SpinGadget, GetGadgetState(#SpinGadget) - 1)
      EndSelect
    EndIf
  EndIf
  
  gtk_main_do_event_(*Event)
EndProcedure 

OpenWindow(0, 100, 100, 180, 70, "SpinGadget")

SpinGadget(#SpinGadget, 65, 20, 60, 25, 0, 100, #PB_Spin_Numeric)
SetActiveGadget(#SpinGadget)
SetGadgetState(#SpinGadget, 50)

; ----- Set event handler to intercept mouse wheel scroll events
gdk_event_handler_set_(@MouseWheelCallback(), 0, 0)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow

gdk_event_handler_set_(0, 0, 0)
Post Reply