| ##===-- 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() |