UI code refactoring (sorry, first time using curses...)

master
Muges 11 years ago
parent a82d87b140
commit 8376dd6ed4
  1. 2
      readme.md
  2. 1
      sounds.py
  3. 282
      ui.py

@ -13,7 +13,7 @@ The program depends on :
## Manual ## Manual
- `Up`/`Down` to change select the master Volume or a track - `Up`/`Down` to select a track or a preset
- `Left`/`Right` to change the volume of the selected track - `Left`/`Right` to change the volume of the selected track
- `q` to quit - `q` to quit

@ -124,6 +124,7 @@ class Preset:
a mastervolume object. a mastervolume object.
""" """
self.filename = filename self.filename = filename
self.name = os.path.splitext(os.path.basename(filename))[0]
self.master = master self.master = master
self.volumes = {} self.volumes = {}

282
ui.py

@ -25,112 +25,212 @@
import curses import curses
class SoundsPad: class OneLineWidget:
""" """
Pad containing the list of tracks, and a volume sliders for each Abstract class representing a one line widget
one of them
""" """
def __init__(self): def __init__(self, parent):
# Width taken by the track titles """
self.namesw = 0 Initialize the widget.
# Width taken by the volume slider - parent is the curses.window object in which the widget will be
self.slidew = 0 drawn
"""
self.parent = parent
# Index of the selected volume slider def draw(self, y, width, selected=False):
self.selection = 1 """
# First line of the sounds pad Abstract method used to draw the widget.
self.top = 0
self.maxtop = 0
# Total height of the pad - y is the number of the line at which the widget will be drawn.
self.height = 1 - selected is a boolean indicating if the widget is selected if
the parent is a ScrollableList
"""
raise NotImplementedError()
def get_selection(self): def on_key(self, c):
""" """
Return the Volume object that is currently selected Method called when the user types the key c and the widget is
selected.
""" """
if self.selection == 0: return
return self.mastervolume
class VolumeWidget(OneLineWidget):
"""
Widget used to set the volume of a Volume object
"""
def __init__(self, parent, volume, namesw):
"""
Initialize the object.
- parent is the curses.window object in which the widget will be
drawn
- volume is a Volume object
- namesw is the width taken by the names of the Volume objects
"""
OneLineWidget.__init__(self, parent)
self.volume = volume
self.namesw = namesw
def draw(self, y, width, selected=False):
# Highlight the name if the widget is selected
if selected:
attribute = curses.A_REVERSE
else: else:
return self.mastervolume.get_sound(self.selection-1) attribute = 0
def set_selection(self, index): # Draw the name
self.selection = max(0, min(index, len(self.mastervolume.get_sounds()))) self.parent.addstr(y, 0, " "+self.volume.name+" ", attribute)
self.top = max(0, min(self.selection - self.screenheight/2, self.maxtop))
def next_track(self): # Position and width of the slider
self.set_selection(self.selection+1) slidex = self.namesw+5
slidew = width-slidex-2-1
slidewleft = (self.volume.get_volume()*slidew)/100
slidewright = slidew-slidewleft
def previous_track(self): # Draw the slider
self.set_selection(self.selection-1) self.parent.addstr(y, slidex-2, "[ ")
self.parent.addstr(y, slidex+slidew, " ]")
def start(self): self.parent.addstr(y, slidex, "#"*slidewleft)
self.pad = curses.newpad(1,1) self.parent.addstr(y, slidex+slidewleft, "-"*slidewright)
def resize(self, height, width): def on_key(self, c):
self.screenheight = height if c == curses.KEY_LEFT:
# Increase the volume
self.volume.inc_volume(-1)
elif c == curses.KEY_RIGHT:
# Decrease the volume
self.volume.inc_volume(1)
class ScrollableList:
"""
Object representing a list of OneLineWidgets that can be browsed and
scrolled through
"""
def __init__(self):
self.set_widgets([])
# Position of the sliders # Number of the line which is at the top of the widget (used for
self.slidex = self.namesw+5 # scrolling)
self.top = 0
# Width of the sliders self.pad = curses.newpad(1,1)
self.slidew = width-self.namesw-7
self.pad.resize(self.height, width+1) def set_widgets(self, widgets, default=0):
self.maxtop = max(0, self.height-height-1) self.widgets = widgets
self.top = min(self.top, self.maxtop) self.set_selection(default)
self.height = len(widgets)
def run(self, mastervolume): def get_selection(self):
self.mastervolume = mastervolume try:
return self.widgets[self.selection]
sounds = mastervolume.get_sounds() except KeyError:
self.namesw = max(max([len(s.name) for s in sounds]), len(mastervolume.name)) return None
self.height = len(sounds)+2
def create_volume_slider(self, y, sound, index): def set_selection(self, selection):
""" """
Creates a volume slider for the Volume object `sound` Set the selection, ensuring that the selected widget exists.
""" """
# Display the name of the sound # Ensure that the widget is in the list
if index == self.selection: selection = min(selection, len(self.widgets)-1)
# Highlight the name of the selected track
attribute = curses.A_REVERSE if selection < 0:
selection = 0
else: else:
attribute = 0 # If the widget is None, find the first widget before the
# selection that is not None
while (selection >= 0 and self.widgets[selection] == None):
selection -= 1
if selection < 0:
# If there isn't one, find the first widget after the
# selection that is not None
selection = 0
while (selection < len(self.widgets) and self.widgets[selection] == None):
selection += 1
# If there still isn't one (all the widgets are equal to
# None, set the selection to 0
if selection >= len(self.widgets):
selection = 0
self.selection = selection
def select_previous_widget(self):
"""
Select the first widget different to None preceding the current
selection
"""
selection = self.selection-1
while (selection >= 0 and self.widgets[selection] == None):
selection -= 1
self.pad.addstr(y, 0, " "+sound.name+" ", attribute) self.set_selection(selection)
# Draw a volume slider : [ ####----- ] def select_next_widget(self):
self.pad.addstr(y, self.slidex-2, "[ ") """
self.pad.addstr(y, self.slidex+self.slidew, " ]") Select the first widget different to None following the current
selection
"""
selection = self.selection+1
while (selection < len(self.widgets) and self.widgets[selection] == None):
selection += 1
slidewleft = (sound.get_volume()*self.slidew)/100 self.set_selection(selection)
slidewright = self.slidew-slidewleft
self.pad.addstr(y, self.slidex, "#"*slidewleft)
self.pad.addstr(y, self.slidex+slidewleft, "-"*slidewright)
def update(self, stop, sleft, sbottom, sright): def draw(self, stop, sleft, sbottom, sright):
""" """
Update the pad Draw the list in the portion of the screen delimited by the
coordinates (stop, sleft, sbottom, sright)
""" """
height = sbottom-stop
width = sright-sleft
self.pad.clear() self.pad.clear()
self.pad.resize(self.height, width)
# Draw each widget
y = 0
for w in self.widgets:
if w != None:
w.draw(y, width, y == self.selection)
y += 1
ptop = max(0, min(self.selection - height/2, self.height-height-1))
self.pad.refresh(ptop, 0, stop, sleft, sbottom, sright)
def on_key(self, c):
if c == curses.KEY_DOWN:
self.select_next_widget()
elif c == curses.KEY_UP:
self.select_previous_widget()
else:
selection = self.get_selection()
if selection != None:
selection.on_key(c)
# Draw the master volume slider class VolumeList(ScrollableList):
self.create_volume_slider(0, self.mastervolume, 0) """
List of VolumeWidgets
"""
def __init__(self, mastervolume):
ScrollableList.__init__(self)
# Draw a slider for each sound sounds = mastervolume.get_sounds()
index = 1 namesw = max(max([len(s.name) for s in sounds]), len(mastervolume.name))
for s in self.mastervolume.get_sounds():
self.create_volume_slider(index+1, s, index) widgets = []
index += 1 widgets.append(VolumeWidget(self.pad, mastervolume, namesw))
widgets.append(None)
for sound in sounds:
widgets.append(VolumeWidget(self.pad, sound, namesw))
self.pad.refresh(self.top, 0, stop, sleft, sbottom, sright) self.set_widgets(widgets, 2)
class UI: class UI:
def __init__(self):
self.soundspad = SoundsPad()
def start(self): def start(self):
""" """
Start the application Start the application
@ -141,8 +241,6 @@ class UI:
curses.cbreak() curses.cbreak()
self.screen.keypad(1) self.screen.keypad(1)
curses.curs_set(0) curses.curs_set(0)
self.soundspad.start()
self.resize() self.resize()
@ -180,52 +278,40 @@ class UI:
self.hpadding = 1 self.hpadding = 1
self.vpadding = 1 self.vpadding = 1
self.soundspad.resize(self.screenh-2*self.vpadding-1, self.screenw-2*self.hpadding-1)
def update(self): def update(self):
""" """
Update the screen Update the screen
""" """
self.screen.clear() self.screen.clear()
self.screen.refresh() self.screen.refresh()
self.soundspad.update(self.vpadding, self.hpadding,
self.screenh-self.vpadding-1, self.volumelist.draw(self.vpadding, self.hpadding,
self.screenw-self.hpadding-1) self.screenh-self.vpadding-1,
self.screenw-self.hpadding-1)
def run(self, mastervolume): def run(self, mastervolume):
""" """
Start the main loop Start the main loop
""" """
self.mastervolume = mastervolume self.volumelist = VolumeList(mastervolume)
self.soundspad.run(mastervolume)
self.resize() self.resize()
self.update() self.update()
while True: while True:
# Wait for user input # Wait for user input and handle it
c = self.screen.getch() c = self.screen.getch()
if c == ord('q'): if c == ord('q'):
# Quit # Quit
self.end() self.end()
break break
elif c == curses.KEY_DOWN:
# Select the next volume slider
self.soundspad.next_track()
self.update()
elif c == curses.KEY_UP:
# Select the previous volume slider
self.soundspad.previous_track()
self.update()
elif c == curses.KEY_LEFT:
# Increase the volume
self.soundspad.get_selection().inc_volume(-1)
self.update()
elif c == curses.KEY_RIGHT:
# Decrease the volume
self.soundspad.get_selection().inc_volume(1)
self.update()
elif c == curses.KEY_RESIZE: elif c == curses.KEY_RESIZE:
# The terminal has been resized, update the display # The terminal has been resized, update the display
self.resize() self.resize()
self.update() self.update()
else:
# Propagate the key to the VolumeList
self.volumelist.on_key(c)
self.update()

Loading…
Cancel
Save