import collections
import curses
import threading
class _Catalog(object):
def __init__(self, name):
self.name = name
self.fold = False
self.active = False
self.items = []
self.cur_key = None
def add_item(self, bundle):
self.items.append(bundle)
class _Pad(object):
def __init__(self, height, width):
self.width = width
self.height = height
self.pad = curses.newpad(height, width)
self.cur_y = 1
def box(self):
self.pad.box()
def reset(self):
self.pad.clear()
self.cur_y = 1
def resize(self, height, width):
self.width = width
self.height = height
self.pad.resize(height, width)
def println(self, text, align='left', style=curses.A_NORMAL):
if self.cur_y > self.height - 2:
return
if len(text) > (self.width - 2):
text = text[:(self.width - 2)]
if align == 'left':
self.pad.addstr(self.cur_y, 1, text.ljust(self.width - 2), style)
elif align == 'center':
self.pad.addstr(self.cur_y, 1, text.center(self.width - 2), style)
self.cur_y += 1
def refresh(self, pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol):
self.pad.refresh(pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol)
class _PanelBase(object):
def __init__(self, screen, height, width, x=0, y=0):
self.screen = screen
self.height = height
self.width = width
self.pad = _Pad(self.height, self.width)
self.resize(self.height, self.width)
self.x, self.y = x, y
self.keypress_listeners = {}
def resize(self, height, width):
self.height = height
self.width = width
self.pad.resize(height, width)
def add_keypress_listener(self, name, key, callback):
if name not in self.keypress_listeners:
self.keypress_listeners[name] = {'key': key, 'callback': callback}
[docs]class TextPanel(_PanelBase):
"""
Curses panel contains only block of text.
"""
def __init__(self, screen, height, width, x=0, y=0):
"""
:param screen: Curses screen to draw this panel on.
:param height: Height of the panel.
:param width: Width of the panel.
:param x: X position of the panel.
:param y: Y position of the panel.
"""
super(TextPanel, self).__init__(screen, height, width, x=x, y=y)
self.content = None
self.cur_key = (None, None)
self.select_cb = None
[docs] def clear(self):
"""
Clear panel content.
"""
self.content = None
[docs] def set_content(self, bundle):
"""
Set panel content.
:param bundle: Content to be set.
"""
self.content = bundle
[docs] def draw(self, active=False):
"""
Draw the text panel.
:param active: If set to true, draw the panel with surrounding box.
"""
self.pad.reset()
lines = str(self.content).splitlines()
for line in lines:
self.pad.println(line, align='left', style=curses.A_NORMAL)
if active:
self.pad.box()
self.pad.refresh(0, 0,
self.y, self.x,
self.y + self.height, self.x + self.width)
[docs] def on_keypress(self, key):
"""
Event handler when keypress event received by the panel.
:param key: Key being pressed.
"""
pass
[docs]class ListPanel(_PanelBase):
"""
Curses panel contains list of entries.
"""
def __init__(self, screen, height, width, x=0, y=0, format_str=''):
"""
:param screen: Curses screen to draw this panel on.
:param height: Height of the panel.
:param width: Width of the panel.
:param x: X position of the panel.
:param y: Y position of the panel.
:param format_str: The template to format a list entry.
"""
super(ListPanel, self).__init__(screen, height, width, x=x, y=y)
self.catalogs = collections.OrderedDict()
self.cur_key = (None, None)
self.select_cb = None
self.format_str = format_str
[docs] def set_select_callback(self, callback):
"""
Set callback function triggered when an list item is selected.
:param callback: Select callback function.
"""
self.select_cb = callback
[docs] def clear(self):
"""
Clear panel content.
"""
self.catalogs = collections.OrderedDict()
[docs] def select(self, cat_key=None, item_key=None):
"""
Select specified item.
:param cat_key: Catalog name of the item for selection.
:param item_key: Item name of the item for selection.
"""
self.cur_key = (cat_key, item_key)
if self.select_cb is not None:
self.select_cb(cat_key, item_key)
[docs] def add_item(self, bundle, catalog=''):
"""
Add an item the list panel.
:param bundle: Content of added item.
:param catalog: Catalog of added item.
"""
if catalog not in self.catalogs:
self.catalogs[catalog] = _Catalog(catalog)
cat = self.catalogs[catalog]
cat.add_item(bundle)
[docs] def draw(self, active=False):
"""
Draw the list panel.
:param active: If set to true, draw the panel with surrounding box.
"""
# Select one item if available
cur_cat, cur_item = self.cur_key
if cur_cat is None or cur_item is None:
cat_name = None
item_idx = None
if self.catalogs:
# pylint: disable=unsubscriptable-object
cat_name = self.catalogs.keys()[0]
items = self.catalogs[cat_name].items
if items:
item_idx = 0
self.select(cat_name, item_idx)
cur_cat, cur_item = self.cur_key
if self.catalogs:
self.pad.reset()
for cat_name, cat in self.catalogs.items():
# Draw catalog title bar
cat_selected = False
if cat_name == cur_cat:
cat_selected = True
cat_style = curses.color_pair(1)
else:
cat_style = curses.color_pair(2)
if cat_name != '':
self.pad.println(cat.name.upper(),
align='center', style=cat_style)
# Draw items
if not cat.fold:
for item_idx, item in enumerate(cat.items):
if item_idx == cur_item and cat_selected:
item_style = curses.color_pair(3)
else:
item_style = curses.A_NORMAL
self.pad.println(self.format_str.format(**item),
style=item_style)
if active:
self.pad.box()
self.pad.refresh(0, 0,
self.y, self.x,
self.y + self.height, self.x + self.width)
[docs] def on_keypress(self, key):
"""
Event handler when keypress event received by the panel.
:param key: Key being pressed.
"""
if key == ord('j'):
cat_name, item_idx = self.cur_key
# pylint: disable=unsubscriptable-object
cats = self.catalogs.keys()
cat = self.catalogs[cat_name]
if item_idx < len(cat.items) - 1:
item_idx += 1
else:
cat_idx = list(cats).index(cat_name)
cat_name = cats[(cat_idx + 1) % len(cats)]
item_idx = 0
self.select(cat_name, item_idx)
elif key == ord('k'):
cat_name, item_idx = self.cur_key
# pylint: disable=unsubscriptable-object
cats = self.catalogs.keys()
cat = self.catalogs[cat_name]
if item_idx > 0:
item_idx -= 1
else:
cat_idx = list(cats).index(cat_name)
cat_name = cats[(cat_idx - 1) % len(cats)]
item_idx = 0
self.select(cat_name, item_idx)
for listener in self.keypress_listeners.values():
if key == ord(listener['key']):
callback_thread = threading.Thread(
target=listener['callback'],
args=(self,)
)
callback_thread.start()