My personal crusade against gtk+3 SpinCtrl's size

In my continuing crusade against the monstrous Gtk+3 SpinCtrl / SpinButton and SpinCtrlDouble, I am attaching my replacement code.
Namely:

minispinctrl.py

minispinfloat.py

minispinbutton.py

minispinctrl

minispinctrl.png

minifloatspin

minifloatspin.png

minispinbutton with a textctrl

minispinbutton_with_textctrl.png

They all attempt to emulate under Gtk3 the look and feel that the originals under Gtk2 had.

They, of course, work under Gtk2 as well.

Hopefully someone else will find them useful.

Feel free to report any/all bugs

Regards

Rolf

minifloatspin.py (24.1 KB)

minispinctrl.py (21.7 KB)

minispinbutton.py (15.4 KB)

2 Likes

Hi Rolf - I really like your GTK spinners but am designing for a 7" touchscreen. So spins and sliders too are way too small for fingertip operation. My workarounds look clumsy. If anyone is addressing problems like this, I would really like to know. Thanks, Graham.

Hi Rolf, thank you, this is an excellent solution to an issue that bothered me for ages and caused endless pages of spam warnings ā€œgtk_box_gadget_distribute: assertion ā€˜size >= 0ā€™ failed in GtkSpinButtonā€. One tweak Iā€™d suggest, when replacing the number you want to be able to highlight the contents and start typing which should then replace whatever you have selected. The change below is needed to do this.

#create replacement text comprising old text and the new character
# Allowing for the insertion point being changed by the keys
# Delete, Backspace and Left, Right arrow keys
lsel = obj.GetSelection()[0]; rsel = obj.GetSelection()[1]
selected = rsel - lsel
if selected == 0:
    new_text = curr_text[:pos]+chr(key)+curr_text[pos:]
#Remove whole string if selected
elif selected == len(curr_text):
    new_text = chr(key)
#Otherwise remove selected bit
else:
    new_text = curr_text[:lsel]+chr(key)+curr_text[rsel:]

take a time out with this speed spin (fully customizable!!!) :joy:

import wx

class Gui(wx.Frame):

    def __init__(self, parent):
        super().__init__(parent, title='Speed Spin')

        pnl = wx.Panel(self)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add(SpSpin(pnl, size=(50, -1)), 0, wx.ALL, 50)
        pnl.SetSizer(hbox)
        self.Centre()
        self.Show()

class SpSpin(wx.TextCtrl):

    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.SetToolTip('you may use the wheel or keys..')

        def evt_text(evt):
            txt = evt.GetString()
            if txt.lstrip('-').isnumeric():
                self.txt = int(txt)
            else:
                self.txt = 0
                self.ChangeValue('')
        def evt_mouse_wh(evt):
            wr = evt.GetWheelRotation()
            upd_txt(True) if wr > 0 else upd_txt(False)
        wxk = (wx.WXK_UP, wx.WXK_DOWN,
                            wx.WXK_RIGHT, wx.WXK_LEFT)
        wxk_up = (wx.WXK_UP, wx.WXK_RIGHT)
        def evt_key_down(evt):
            if (kc := evt.GetKeyCode()) in wxk:
                upd_txt(True) if kc in wxk_up else upd_txt(False)
            evt.Skip()
        def upd_txt(up):
            if up:
                self.txt += 1
            else:
                self.txt -= 1
            self.ChangeValue(str(self.txt))
            wx.CallAfter(self.SetInsertionPoint, self.GetLastPosition())

        self.Bind(wx.EVT_TEXT, evt_text)
        self.Bind(wx.EVT_MOUSEWHEEL, evt_mouse_wh)
        self.Bind(wx.EVT_KEY_DOWN, evt_key_down)
        self.txt = 0

app = wx.App()
Gui(None)
app.MainLoop()

@edwardsmith999 I have added your requested tweak
Iā€™ve also amended the insertion point, so that when replacing, the next input character is inserted in the right place. Previously, it always jumped to the end.
Give it a test and let me know if Iā€™ve introduced any issues.
Regards,
Rolf
minispinctrl.py (28.4 KB) minifloatspin.py (31.5 KB) minispinbutton.py (21.3 KB)

@edwardsmith999

After you brought it to my attention again, I found a glitch of my own. :grimacing:

Changelog:
1.1
Ability to replace highlighted numbers in the control suggested by edwardsmith999
Given the above change, adjust the insertion point, so that when replacing, the next input character is inserted in the right place

Override SetBackgroundColour and SetForegroundColour to ensure that the text colour changes automatically to black or white depending on the background colour and the scroll arrow image changes appropriately as well.

Sorry about the glitch!
minispinctrl.py (29.4 KB)
minifloatspin.py (32.6 KB)

Hi @rolfofsaxony, thanks for the update, this might be worth putting on git to increase visibility (as well as track these changes). I sure this forum didnā€™t come up when I looked for solutions to this previously.

In the interest of developing this further, I also had to add the following function to swap out for spinctrl with integers.

def GetInt(self):
    """
    Retrieve the integer of the control at the time
    this event was generated."""
    return int(self.value)

Out of interest, how does this differ from GetValue()?
Unless you are referring specifically to minifloatspin and even then, getting the integer from the float, if you need it, is straightforward.
Iā€™ll add it if there is a compelling reason, as Iā€™ve found a small bug that will result in an update anyway.
Bug you can input a digit before the minus sign, resulting in a Value error

The latest minispinctrl.

Changelog:
1.3     Fix bug which allowed input like 2-7, typing a digit in front of minus sign

1.2     Subclassing migrates from wx.Control to wx.Panel to enable Tab_Traversal.
        `Alignment` Left, if desired should by performed with SetStyle()

minifloatspin.py (34.1 KB) minispinctrl.py (30.4 KB)

Iā€™m not sure, I guess just GetValue with rounding so it ensures output is an integer. It was part of the API for the SpinCtrl so I used it as I wanted integers (and given spin control goes in increments, I guess integer increments are the most common so maybe people would expect this).

@edwardsmith999 It isnā€™t a problem, I simply canā€™t find the API you are referring to.
Here are the updated versions with minifloatspin.py having a GetInt() function.
There are a few more improvements:
minifloatspin

Changelog:
1.4     Change formatting from old style % modulo to f-strings (formatted string literals)
        This change requires you run python3 but given that even python3.6 reached end of life
         at the end of 2021, I think that is a resonable position to take.

        Added a new function GetInt() return the integer in the control, because it was requested

        The halfway click point for the image is calculated more accurately, by basing image size on the textctrl
        after it has been assigned text.

        Click and hold the SpinCtrl image, will repeat the increment, whilst Left Mouse button is held down.
         (The Ctrl Key x 10 adjustment is still honoured)

minifloatspin.py (35.0 KB)

minispinctrl

Changelog:
1.4     The halfway click point for the image is calculated more accurately, by basing image size on the textctrl
         after it has been assigned text.

        Click and hold the SpinCtrl image, will repeat the increment, whilst Left Mouse button is held down.

minispinctrl.py (30.8 KB)

minispinbutton
minispinbutton.py gets the same Click and hold the SpinCtrl image treatment, which will repeat the increment, whilst Left Mouse button is held down.
minispinbutton.py (21.4 KB)

Let me know if Iā€™ve introduced issues.
Regards,
Rolf