diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2016-01-06 20:12:03 +0000 | 
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2016-01-06 20:12:03 +0000 | 
| commit | 9e6d35490a6542f9c97607f93c2ef8ca8e03cbcc (patch) | |
| tree | dd2a1ddf0476664c2b823409c36cbccd52662ca7 /utils/lui/cui.py | |
| parent | 3bd2e91faeb9eeec1aae82c64a3253afff551cfd (diff) | |
Notes
Diffstat (limited to 'utils/lui/cui.py')
| -rwxr-xr-x | utils/lui/cui.py | 320 | 
1 files changed, 320 insertions, 0 deletions
| diff --git a/utils/lui/cui.py b/utils/lui/cui.py new file mode 100755 index 000000000000..e82f0abac2cc --- /dev/null +++ b/utils/lui/cui.py @@ -0,0 +1,320 @@ +##===-- cui.py -----------------------------------------------*- Python -*-===## +## +##                     The LLVM Compiler Infrastructure +## +## This file is distributed under the University of Illinois Open Source +## License. See LICENSE.TXT for details. +## +##===----------------------------------------------------------------------===## + +import curses +import curses.ascii +import threading + +class CursesWin(object): +  def __init__(self, x, y, w, h): +    self.win = curses.newwin(h, w, y, x) +    self.focus = False + +  def setFocus(self, focus): +    self.focus = focus +  def getFocus(self): +    return self.focus +  def canFocus(self): +    return True + +  def handleEvent(self, event): +    return +  def draw(self): +    return + +class TextWin(CursesWin): +  def __init__(self, x, y, w): +    super(TextWin, self).__init__(x, y, w, 1) +    self.win.bkgd(curses.color_pair(1)) +    self.text = '' +    self.reverse = False + +  def canFocus(self): +    return False + +  def draw(self): +    w = self.win.getmaxyx()[1] +    text = self.text +    if len(text) > w: +      #trunc_length = len(text) - w +      text = text[-w+1:] +    if self.reverse: +      self.win.addstr(0, 0, text, curses.A_REVERSE) +    else: +      self.win.addstr(0, 0, text) +    self.win.noutrefresh() + +  def setReverse(self, reverse): +    self.reverse = reverse  + +  def setText(self, text): +    self.text = text + +class TitledWin(CursesWin): +  def __init__(self, x, y, w, h, title): +    super(TitledWin, self).__init__(x, y+1, w, h-1) +    self.title = title +    self.title_win = TextWin(x, y, w) +    self.title_win.setText(title) +    self.draw() + +  def setTitle(self, title): +    self.title_win.setText(title) + +  def draw(self): +    self.title_win.setReverse(self.getFocus()) +    self.title_win.draw() +    self.win.noutrefresh() + +class ListWin(CursesWin): +  def __init__(self, x, y, w, h): +    super(ListWin, self).__init__(x, y, w, h) +    self.items = [] +    self.selected = 0 +    self.first_drawn = 0 +    self.win.leaveok(True) + +  def draw(self): +    if len(self.items) == 0: +      self.win.erase() +      return + +    h, w = self.win.getmaxyx() + +    allLines = [] +    firstSelected = -1 +    lastSelected = -1 +    for i, item in enumerate(self.items): +      lines = self.items[i].split('\n') +      lines = lines if lines[len(lines)-1] != '' else lines[:-1] +      if len(lines) == 0: +        lines = [''] + +      if i == self.getSelected(): +        firstSelected = len(allLines) +      allLines.extend(lines) +      if i == self.selected: +        lastSelected = len(allLines) - 1 + +    if firstSelected < self.first_drawn: +      self.first_drawn = firstSelected +    elif lastSelected >= self.first_drawn + h: +      self.first_drawn = lastSelected - h + 1 + +    self.win.erase() + +    begin = self.first_drawn +    end = begin + h + +    y = 0 +    for i, line in list(enumerate(allLines))[begin:end]: +      attr = curses.A_NORMAL +      if i >= firstSelected and i <= lastSelected: +        attr = curses.A_REVERSE +        line = '{0:{width}}'.format(line, width=w-1) + +      # Ignore the error we get from drawing over the bottom-right char. +      try: +        self.win.addstr(y, 0, line[:w], attr) +      except curses.error: +        pass +      y += 1 +    self.win.noutrefresh() + +  def getSelected(self): +    if self.items: +      return self.selected +    return -1 + +  def setSelected(self, selected): +    self.selected = selected +    if self.selected < 0: +      self.selected = 0 +    elif self.selected >= len(self.items): +      self.selected = len(self.items) - 1 + +  def handleEvent(self, event): +    if isinstance(event, int): +      if len(self.items) > 0: +        if event == curses.KEY_UP: +          self.setSelected(self.selected - 1) +        if event == curses.KEY_DOWN: +          self.setSelected(self.selected + 1) +        if event == curses.ascii.NL: +          self.handleSelect(self.selected) + +  def addItem(self, item): +    self.items.append(item) + +  def clearItems(self): +    self.items = [] + +  def handleSelect(self, index): +    return + +class InputHandler(threading.Thread): +  def __init__(self, screen, queue): +    super(InputHandler, self).__init__() +    self.screen = screen +    self.queue = queue + +  def run(self): +    while True: +      c = self.screen.getch() +      self.queue.put(c) + + +class CursesUI(object): +  """ Responsible for updating the console UI with curses. """ +  def __init__(self, screen, event_queue): +    self.screen = screen +    self.event_queue = event_queue + +    curses.start_color() +    curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) +    curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK) +    curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) +    self.screen.bkgd(curses.color_pair(1)) +    self.screen.clear() + +    self.input_handler = InputHandler(self.screen, self.event_queue) +    self.input_handler.daemon = True + +    self.focus = 0 + +    self.screen.refresh() + +  def focusNext(self): +    self.wins[self.focus].setFocus(False) +    old = self.focus +    while True: +      self.focus += 1 +      if self.focus >= len(self.wins): +        self.focus = 0 +      if self.wins[self.focus].canFocus(): +        break +    self.wins[self.focus].setFocus(True) + +  def handleEvent(self, event): +    if isinstance(event, int): +      if event == curses.KEY_F3: +        self.focusNext() + +  def eventLoop(self): + +    self.input_handler.start() +    self.wins[self.focus].setFocus(True) + +    while True: +      self.screen.noutrefresh() + +      for i, win in enumerate(self.wins): +        if i != self.focus: +          win.draw() +      # Draw the focused window last so that the cursor shows up. +      if self.wins: +        self.wins[self.focus].draw() +      curses.doupdate() # redraw the physical screen + +      event = self.event_queue.get() + +      for win in self.wins: +        if isinstance(event, int): +          if win.getFocus() or not win.canFocus(): +            win.handleEvent(event) +        else: +          win.handleEvent(event) +      self.handleEvent(event) + +class CursesEditLine(object): +  """ Embed an 'editline'-compatible prompt inside a CursesWin. """ +  def __init__(self, win, history, enterCallback, tabCompleteCallback): +    self.win = win +    self.history = history +    self.enterCallback = enterCallback +    self.tabCompleteCallback = tabCompleteCallback + +    self.prompt = '' +    self.content = '' +    self.index = 0 +    self.startx = -1 +    self.starty = -1 + +  def draw(self, prompt=None): +    if not prompt: +      prompt = self.prompt +    (h, w) = self.win.getmaxyx() +    if (len(prompt) + len(self.content)) / w + self.starty >= h-1: +      self.win.scroll(1) +      self.starty -= 1 +      if self.starty < 0: +        raise RuntimeError('Input too long; aborting') +    (y, x) = (self.starty, self.startx) + +    self.win.move(y, x) +    self.win.clrtobot() +    self.win.addstr(y, x, prompt) +    remain = self.content +    self.win.addstr(remain[:w-len(prompt)]) +    remain = remain[w-len(prompt):] +    while remain != '': +      y += 1 +      self.win.addstr(y, 0, remain[:w]) +      remain = remain[w:] + +    length = self.index + len(prompt) +    self.win.move(self.starty + length / w, length % w) + +  def showPrompt(self, y, x, prompt=None): +    self.content = '' +    self.index = 0 +    self.startx = x +    self.starty = y +    self.draw(prompt) + +  def handleEvent(self, event): +    if not isinstance(event, int): +      return # not handled +    key = event + +    if self.startx == -1: +      raise RuntimeError('Trying to handle input without prompt') + +    if key == curses.ascii.NL: +      self.enterCallback(self.content) +    elif key == curses.ascii.TAB: +      self.tabCompleteCallback(self.content) +    elif curses.ascii.isprint(key): +      self.content = self.content[:self.index] + chr(key) + self.content[self.index:] +      self.index += 1 +    elif key == curses.KEY_BACKSPACE or key == curses.ascii.BS: +      if self.index > 0: +        self.index -= 1 +        self.content = self.content[:self.index] + self.content[self.index+1:] +    elif key == curses.KEY_DC or key == curses.ascii.DEL or key == curses.ascii.EOT: +      self.content = self.content[:self.index] + self.content[self.index+1:] +    elif key == curses.ascii.VT: # CTRL-K +      self.content = self.content[:self.index] +    elif key == curses.KEY_LEFT or key == curses.ascii.STX: # left or CTRL-B +      if self.index > 0: +        self.index -= 1 +    elif key == curses.KEY_RIGHT or key == curses.ascii.ACK: # right or CTRL-F +      if self.index < len(self.content): +        self.index += 1 +    elif key == curses.ascii.SOH: # CTRL-A +      self.index = 0 +    elif key == curses.ascii.ENQ: # CTRL-E +      self.index = len(self.content) +    elif key == curses.KEY_UP or key == curses.ascii.DLE: # up or CTRL-P +      self.content = self.history.previous(self.content) +      self.index = len(self.content) +    elif key == curses.KEY_DOWN or key == curses.ascii.SO: # down or CTRL-N +      self.content = self.history.next() +      self.index = len(self.content) +    self.draw() | 
