I'm using urwid, which is a Python "framework" for designing terminal user interfaces in ncurses. There's one thing though that I'm not able to do in urwid that was easy in curses - make the cursor invisible. As it is now, the cursor is visible when selecting buttons, and it just looks plain ugly. Is there a way to disable it?
I agree that the flashing cursor on an urwid.Button looks a bit lame, so I've come up with a solution to hide it. In urwid, the Button class is just a subclass of WidgetWrap containing a SelectableIcon and two Text widgets (the enclosing "<" and ">"). It's the SelectableIcon class that sets the cursor position to the first character of the label, by default. By subclassing SelectableIcon, modifying the cursor position and then wrapping it into an urwid.WidgetWrap subclass you can create your own custom button that can do all the tricks a built-in Button, or even more.
Here' what it looks like in my project.

import urwid
class ButtonLabel(urwid.SelectableIcon):
def __init__(self, text):
"""
Here's the trick:
we move the cursor out to the right of the label/text, so it doesn't show
"""
curs_pos = len(text) + 1
urwid.SelectableIcon.__init__(self, text, cursor_position=curs_pos)
Next, you can wrap a ButtonLabel object along with any other objects into a WidgetWrap subclass that will be your custom button class.
class FixedButton(urwid.WidgetWrap):
_selectable = True
signals = ["click"]
def __init__(self, label):
self.label = ButtonLabel(label)
# you could combine the ButtonLabel object with other widgets here
display_widget = self.label
urwid.WidgetWrap.__init__(self, urwid.AttrMap(display_widget, None, focus_map="button_reversed"))
def keypress(self, size, key):
"""
catch all the keys you want to handle here
and emit the click signal along with any data
"""
pass
def set_label(self, new_label):
# we can set the label at run time, if necessary
self.label.set_text(str(new_label))
def mouse_event(self, size, event, button, col, row, focus):
"""
handle any mouse events here
and emit the click signal along with any data
"""
pass
In this code, there is actually not much combination of widgets in the FixedButton WidgetWrap subclass, but you could add a "[" and "]" to the edges of the button, wrap it into a LineBox, etc. If all this is superfluous, you can just move the event handling functions into the ButtonLabel class, and make it emit a signal when it gets clicked.
To make the button reversed when the user moves on it, wrap it into AttrMap and set the focus_map to some palette entry ("button_reversed", in my case).
Building upon Drunken Master's answer, I've cleaned up the solution as much as possible.
The urwid.SelectableIcon is basically an urwid.Text field with this ugly blinking cursor.
So instead of overriding the urwid.SelectableIcon and packing it into an urwid.WidgetWrap, let's take an urwid.Text directly and make it selectable and react to button/mouse activation (inspired from urwid's simple menu tutorial):
import urwid
choices = u'Chapman Cleese Gilliam Idle Jones Palin'.split()
class ListEntry(urwid.Text):
_selectable = True
signals = ["click"]
def keypress(self, size, key):
"""
Send 'click' signal on 'activate' command.
"""
if self._command_map[key] != urwid.ACTIVATE:
return key
self._emit('click')
def mouse_event(self, size, event, button, x, y, focus):
"""
Send 'click' signal on button 1 press.
"""
if button != 1 or not urwid.util.is_mouse_press(event):
return False
self._emit('click')
return True
def menu(title, choices):
body = [urwid.Text(title), urwid.Divider()]
for c in choices:
button = ListEntry(c)
urwid.connect_signal(button, 'click', item_chosen, c)
body.append(urwid.AttrMap(button, None, focus_map='reversed'))
return urwid.ListBox(urwid.SimpleFocusListWalker(body))
def item_chosen(button, choice):
response = urwid.Text([u'You chose ', choice, u'\n'])
done = ListEntry(u'Ok')
urwid.connect_signal(done, 'click', exit_program)
main.original_widget = urwid.Filler(urwid.Pile([response,
urwid.AttrMap(done, None, focus_map='reversed')]))
def exit_program(button):
raise urwid.ExitMainLoop()
main = urwid.Padding(menu(u'Pythons', choices), left=2, right=2)
top = urwid.Overlay(main, urwid.SolidFill(u'\N{MEDIUM SHADE}'),
align='center', width=('relative', 60),
valign='middle', height=('relative', 60),
min_width=20, min_height=9)
urwid.MainLoop(top, palette=[('reversed', 'standout', '')]).run()
Works like a charm:

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With