Пример #1
0
 def get_default_size():
     config = TerminalConfig()
     font_name = config.get("Display", "font", "Consolas")
     font_size = config.getint("Display", "fontsize", 11)
     cursor = TerminalCursor(None, font_name, font_size)
     (col_size, row_size) = cursor.get_font_metrics()
     return (80 * col_size, 24 * row_size)
Пример #2
0
 def __init__(self, width=80, height=24, parent=None):
     self.log = log.get_log(self)
     self.width = width      # in cells, not pixels
     self.height = height
     self.parent = parent
     self.config = TerminalConfig()
     self.font_name = self.config.get("Display", "font", "Consolas")
     self.font_size = self.config.getint("Display", "fontsize", 11)
     self.cursor = TerminalCursor(self, self.font_name, self.font_size)
     self.scrollback = self.config.getint("Display", "scrollback", 100)
     self.base = 0
     self.alternate_active = False
     self.create_buffer()
     self.create_alternate_buffer()
     self.setup_timer_events()
     (self.col_size, self.row_size) = self.cursor.get_font_metrics()
Пример #3
0
 def save_cursor(self):
     self.saved_cursor = self.cursor     # DECSC
     self.cursor = TerminalCursor(self, self.font_name, self.font_size)
Пример #4
0
class ScreenBuffer:
    def __init__(self, width=80, height=24, parent=None):
        self.log = log.get_log(self)
        self.width = width      # in cells, not pixels
        self.height = height
        self.parent = parent
        self.config = TerminalConfig()
        self.font_name = self.config.get("Display", "font", "Consolas")
        self.font_size = self.config.getint("Display", "fontsize", 11)
        self.cursor = TerminalCursor(self, self.font_name, self.font_size)
        self.scrollback = self.config.getint("Display", "scrollback", 100)
        self.base = 0
        self.alternate_active = False
        self.create_buffer()
        self.create_alternate_buffer()
        self.setup_timer_events()
        (self.col_size, self.row_size) = self.cursor.get_font_metrics()

    @staticmethod
    def get_default_size():
        config = TerminalConfig()
        font_name = config.get("Display", "font", "Consolas")
        font_size = config.getint("Display", "fontsize", 11)
        cursor = TerminalCursor(None, font_name, font_size)
        (col_size, row_size) = cursor.get_font_metrics()
        return (80 * col_size, 24 * row_size)

    def setup_timer_events(self):
        self.blink_cursor_timer = QtCore.QTimer()
        self.blink_cursor_timer.timeout.connect(self.blink_cursor_cb)
        self.blink_cursor_active = True
        self.draw_cursor = True
        self.blink_speed = self.config.getint("Cursor", "blinkms", 600)
        self.blink_cursor_timer.start(self.blink_speed)

    def blink_cursor_cb(self):
        if not self.blink_cursor_active:
            return
        self.draw_cursor = not self.draw_cursor
        position = self.cursor.position()
        self.parent.update(position)

    def blink_cursor(self, blink=True):
        self.blink_cursor_active = blink
        self.draw_cursor = True
        position = self.cursor.position()
        self.parent.update(position)
        self.reset_blink_timer()

    def show_cursor(self, show=True):
        self.blink_cursor_active = show
        self.draw_cursor = show
        position = self.cursor.position()
        self.parent.update(position)
        self.reset_blink_timer()

    def reset_blink_timer(self):
        self.blink_cursor_timer.start(self.blink_speed)

    def get_widget(self):
        return self.parent

    def create_buffer(self):
        self.buffer = []
        for row in xrange(0, self.height + self.scrollback):
            self.buffer.append(TerminalRow(self.width, self))
        self.log.debug("Buffer size = %s" % len(self.buffer))

    def create_alternate_buffer(self):
        self.alternate = []
        for row in xrange(0, self.height):
            self.alternate.append(TerminalRow(self.width, self))
        self.set_buffer_scroll_range(0, self.height)

    def insert_row(self, num=1):
        buf = self.get_buffer()
        (row, col) = self.cursor.get_row_col()
        new_rows = [TerminalRow(self.width, self) for x in xrange(0, num)]
        buf.insert(row, '')
        buf[row:row + 1] = new_rows
        self.log.debug("Inserted %s row(s): %s" % (num, len(buf)))

        # delete rows that are greater than the scroll_bottom
        scroll_top = self.get_scroll_top()
        scroll_bottom = self.get_scroll_bottom()
        del buf[scroll_bottom:scroll_bottom + num]
        self.parent.update()

    def insert_cell(self, row, col):
        buf = self.get_buffer()
        buf[row].insert(col, TerminalCell())
        if len(buf[row]) > self.width:
            del buf[row][self.width - 1:]

    def delete_row(self, num=1):
        buf = self.get_buffer()
        (row, col) = self.cursor.get_row_col()
        del buf[row:row + num]
        self.log.debug("Deleted %s row(s): %s" % (num, len(buf)))

        # insert new blank rows
        scroll_bottom = self.get_scroll_bottom() - num
        new_rows = [TerminalRow(self.width, self) for x in xrange(0, num)]
        buf.insert(scroll_bottom, '')
        buf[scroll_bottom:scroll_bottom + 1] = new_rows
        self.parent.update()

    def get_buffer(self):
        if self.alternate_active:
            return self.alternate
        return self.buffer

    def is_alternate_buffer(self):
        return self.alternate_active

    def resize(self, width, height):
        '''Width and height in cells, not pixels. May change this...'''
        #FIXME creating new rows/cells when the height/width changes is
        # very expensive...investigate other methods of resizing the buffers
        # without having to create new objects each time...
        self.log.debug("Resizing screen to %s, %s" % (width, height))
        if height > self.height:
            diff = height - self.height
            for cnt in xrange(0, diff):
                self.buffer.append(TerminalRow(width, self))
            self.log.debug("Resized buffer size = %s" % len(self.buffer))

        if width > self.width:
            self.log.debug("Increasing width of screen buffer to %s." % width)
            for row in xrange(0, height + self.scrollback):
                self.buffer[row].expand(width)

        if height < len(self.alternate):
            diff = len(self.alternate) - height 
            del self.alternate[-diff:]
            self.log.debug("Deleted %s rows from alt buffer, len = %s" % \
                              (diff, len(self.alternate)))
        elif height > len(self.alternate):
            diff = height - len(self.alternate)
            for cnt in xrange(0, diff):
                self.alternate.append(TerminalRow(width, self))
            self.log.debug("Added %s rows to alt buffer, len = %s" % \
                             (diff, len(self.alternate)))

        if width < len(self.alternate[0]):
            diff = len(self.alternate[0]) - width
            for row in xrange(0, len(self.alternate)):
                del self.alternate[row][-diff:]
                self.alternate[row].width = width
            self.log.debug("Deleted %s cols from alt buffer, len = %s" % \
                              (diff, len(self.alternate[0])))
        elif width > len(self.alternate[0]):
            diff = width - len(self.alternate[0])
            for row in xrange(0, len(self.alternate)):
                self.alternate[row].expand(width)
            self.log.debug("Added %s cols to alt buffer, len = %s" % \
                             (diff, len(self.alternate[0])))

        self.width = width
        self.height = height
        self.buffer_scroll_top = 0
        self.buffer_scroll_bottom = height

    def get_cursor(self):
        return self.cursor

    def save_cursor(self):
        self.saved_cursor = self.cursor     # DECSC
        self.cursor = TerminalCursor(self, self.font_name, self.font_size)

    def restore_cursor(self):
        if not hasattr(self, 'saved_cursor'):
            self.log.warning("Trying to restore unsaved cursor!!!")
            return
        cell = self.cursor.get_cell()
        cell.set_dirty()
        self.cursor = self.saved_cursor
        del self.saved_cursor       # DECRC
        cell = self.cursor.get_cell()
        cell.set_dirty()

    def get_size(self):
        return (self.width, self.height)

    def get_pixel_size(self):
        return (self.width * self.col_size, self.height * self.row_size)

    def get_cells_from_rect(self, rect):
        (top, left) = (rect.top(), rect.left())
        (bottom, right) = (rect.bottom(), rect.right())
        x = left / self.col_size
        y = (top / self.row_size)
        y += self.base
        dx = (right / self.col_size) + 1
        dy = (bottom / self.row_size) + 1
        dy += self.base
        buf = self.get_buffer()
        if dy > len(buf):
            dy = len(buf)
        return (y, x, dy, dx)

    def get_cell_from_point(self, point):
        x = point.x()
        y = point.y()
        ret = (self.base + int(y / self.row_size), int(x / self.col_size))
        if ret[0] < 0:
            ret = (0, ret[1])
        if ret[1] < 0:
            ret = (ret[0], 0)
        return ret

    def create_rect_from_cell(self, row, col):
        top = (row - self.base) * self.row_size
        left = col * self.col_size
        return QtCore.QRect(left, top, self.col_size, self.row_size)

    def draw(self, painter, event):
        (top, left, bottom, right) = self.get_cells_from_rect(event.rect())
        self.log.debug("Redrawing (%s,%s) to (%s,%s)" % (top, left, 
                                                         bottom, right))
        row_range = range(top, bottom)
        row_range.reverse()
        buf = self.get_buffer()
        for row in row_range:
            buf[row].draw(painter, row)

        cursor_pos = self.cursor.position()
        if self.draw_cursor and cursor_pos.intersects(event.rect()):
            self.cursor.draw(painter)

    def is_rect_adjacent(r1, r2):
        if 0 <= r1.top() - r2.bottom() <= 1:
            return True
        if 0 <= r2.top() - r1.bottom() <= 1:
            return True
        if 0 <= r1.left() - r2.right() <= 1:
            return True
        if 0 <= r2.left() - r1.right() <= 1:
            return True
        return False
    is_rect_adjacent = staticmethod(is_rect_adjacent)

    def repaint_dirty_cells(self):
        rect = None
        top = self.base
        bottom = top + self.height
        buf_size = self.get_buffer_size()
        if bottom > buf_size:
            bottom = buf_size
        buf = self.get_buffer()
        for row in xrange(top, bottom):
            for col in xrange(0, self.width):
                try:
                    cell = buf[row][col]
                except:
                    self.log.exception()
                    self.log.error("row = %s, col = %s" % (row, col))
                    return
                if not cell.dirty:
                    continue
                cell.dirty = False
                if rect is None:
                    rect = self.create_rect_from_cell(row, col)
                else:
                    new_rect = self.create_rect_from_cell(row, col)
                    if new_rect.intersects(rect) or \
                       ScreenBuffer.is_rect_adjacent(rect, new_rect):
                        rect = rect.unite(new_rect)
                    else:
                        self.parent.update(rect)
                        rect = new_rect
        if rect is not None:
            self.parent.update(rect)

    def set_window_title(self, title):
        if self.parent is None:
            return
        self.parent.setWindowTitle(title)

    def get_cell(self, row, col):
        '''Retrieves a TerminalCell object from this screen buffer.'''
        buf = self.get_buffer()
        try:
            return buf[row][col]
        except IndexError as e:
            self.log.error("IndexError (%s,%s)" % (row, col))
            raise e

    def scroll(self, direction=ScrollDirection.DOWN, times=1):
        if direction == ScrollDirection.DOWN:
            self.scroll_down(times)
        elif direction == ScrollDirection.UP:
            self.scroll_up(times)
        else:
            self.log.error("Unknown scroll direction")

    def scroll_up(self, times=1):
        if self.alternate_active:
            scroll_top = self.get_scroll_top()
            scroll_bottom = self.get_scroll_bottom()
            buf = self.get_buffer()
            first = scroll_top
            last = scroll_bottom - 1

            rows = [TerminalRow(self.width, self) for x in range(0, times)]
            del buf[last:last + times]
            buf.insert(first, '')
            buf[first:first + 1] = rows

            self.parent.update()
            return

        self.base -= times
        if self.base < 0:
            self.base = 0
        self.log.debug("Scrolling screen buffer, base = %s, row = %s" % \
                       (self.base, self.cursor.row))
        self.parent.update()
        self.parent.set_scroll_value(self.base)

    def scroll_down(self, times=1):
        if self.alternate_active:
            scroll_top = self.get_scroll_top()
            scroll_bottom = self.get_scroll_bottom()
            buf = self.get_buffer()
            first = scroll_top - 1
            if first < 0:
                first = 0
            last = scroll_bottom - 1
            if last > len(buf):
                last = len(buf) - 1

            rows = [TerminalRow(self.width, self) for x in xrange(0, times)]
            del buf[first:first + times]
            buf.insert(last, '')
            buf[last:last + 1] = rows

            #repaint_buf = buf[first:last + 1]
            self.parent.update()
            return

        self.base += times
        self.log.debug("Scrolling screen buffer, base = %s, row = %s" % \
                       (self.base, self.cursor.row))
        if (self.base - times) >= self.scrollback:
            self.log.debug("Scrollback exceeded...rolling over buffer.")
            self.base -= times
            del self.buffer[0:times]
            rows = [TerminalRow(self.width, self) for x in xrange(0, times)]
            self.buffer.extend(rows)
        self.parent.set_scroll_value(self.base)
        self.parent.update()

    def set_buffer_scroll_range(self, top, bottom):
        '''Do not use this to set scroll ranges for the widget. 
           This is used by the sequencer to manipulate text in the buffer.
           It does not affect the graphical scroll bar at all.
           top, bottom should not be zero-offset. 
        '''
        self.buffer_scroll_top = top
        self.buffer_scroll_bottom = bottom

    def clear_screen(self):
        if self.alternate_active:
            for row in self.alternate:
                row.reset()
            return
        self.log.debug("Clear Screen")
        #self.scroll_bar.setRange(0, self.base)
        #self.scroll_bar.setValue(self.base)
        (row, col) = self.cursor.get_row_col()
        times = row - self.base
        if self.base >= self.scrollback:
            self.scroll_down(times)
            self.log.debug("Setting cursor to (%s, %s)" % (row, col))
            self.cursor.set_row_col(self.base, col)
        else:
            self.scroll_down(times)
        #self.parent.set_scroll_value(self.base)

    def get_base_row(self):
        return self.base

    def get_buffer_size(self):
        if self.alternate_active:
            return len(self.alternate)
        # buffer size may actually be larger than height + scrollback...
        return self.height + self.scrollback

    def get_scroll_bottom(self):
        if self.alternate_active:
            if hasattr(self, "buffer_scroll_bottom"):
                return self.buffer_scroll_bottom
        screen_bottom = self.base + self.height
        buf_size = self.get_buffer_size()
        if screen_bottom > buf_size:
            return buf_size
        return buf_size

    def get_scroll_top(self):
        if self.alternate_active:
            if hasattr(self, "buffer_scroll_top"):
                return self.buffer_scroll_top
        return self.base

    def set_alternate_buffer(self, alternate=True):
        self.log.debug("Set alternate buffer: %s" % alternate)
        self.alternate_active = alternate
        if alternate:
            self.saved_scroll_values = self.parent.get_scroll_value()
            self.saved_base = self.base
            self.base = 0
        elif hasattr(self, 'saved_scroll_values'):
            self.parent.set_scroll_value(*self.saved_scroll_values)
            del self.saved_scroll_values
            self.base = self.saved_base
            del self.saved_base
        self.parent.update()

    def print_debug(self):
        # this is an expensive function, so we skip it if we are not logging 
        # debug anyway
        if self.log.get_level() > log.Log.DEBUG:
            return
        debug = ""
        buff = self.buffer if not self.alternate_active else self.alternate
        bottom = len(buff)
        for row in xrange(0, bottom):
            debug += u"%03d: " % row
            for col in xrange(0, self.width):
                debug += unicode(buff[row][col]) or u' '
            debug += u"\n"
        self.log.debug("Screen buffer contents:\n%s" % debug)

    def set_cursor_keys(self, application=True):
        self.application_cursor_keys = application
        self.log.debug("Set application cursor keys: %s" % application)

    def process_keypress(self, event):
        sequence = self.__process_key(event)
        if sequence:
            return sequence
        elif hasattr(self, 'application_cursor_keys') and \
           self.application_cursor_keys:
            return self.__process_application_cursor_keys(event)
        else:
            return self.__process_normal_cursor_keys(event)

    def __process_key(self, event):
        if event.key() == QtCore.Qt.Key_PageUp:
            return '\x1b[5~'
        elif event.key() == QtCore.Qt.Key_PageDown:
            return '\x1b[6~'
        return False

    def __process_normal_cursor_keys(self, event):
        if event.key() == QtCore.Qt.Key_Up:
            return '\x1b[A'
        elif event.key() == QtCore.Qt.Key_Down:
            return '\x1b[B'
        elif event.key() == QtCore.Qt.Key_Right:
            return '\x1b[C'
        elif event.key() == QtCore.Qt.Key_Left:
            return '\x1b[D'
        elif event.key() == QtCore.Qt.Key_Home:
            return '\x1b[H'
        elif event.key() == QtCore.Qt.Key_End:
            return '\x1b[F'
        return False
 
    def __process_application_cursor_keys(self, event):
        if event.key() == QtCore.Qt.Key_Up:
            return '\x1bOA'
        elif event.key() == QtCore.Qt.Key_Down:
            return '\x1bOB'
        elif event.key() == QtCore.Qt.Key_Right:
            return '\x1bOC'
        elif event.key() == QtCore.Qt.Key_Left:
            return '\x1bOD'
        elif event.key() == QtCore.Qt.Key_Home:
            return '\x1bOH'
        elif event.key() == QtCore.Qt.Key_End:
            return '\x1bOF'
        return False

    def clear_selection(self):
        if hasattr(self, 'selection_start'):
            del self.selection_start
        buf = self.get_buffer()
        for row in xrange(0, len(buf)):
            for col in xrange(0, len(buf[row])):
                cell = buf[row][col]
                if cell.selected:
                    cell.toggle_selection()
                    rect = self.create_rect_from_cell(row, col)
                    self.parent.update(rect)

    def set_selection_start(self, top, left):
        self.clear_selection()
        cell = self.get_cell(top, left)
        cell.toggle_selection()
        rect = self.create_rect_from_cell(top, left)
        self.parent.update(rect)
        self.selection_start = (top, left)

    def set_selection_to_cell(self, top, left):
        if not hasattr(self, 'selection_start'):
            return
        self.cursor.save_row_col()
        self.cursor.set_row_col(*self.selection_start)
        while self.cursor.get_row_col() != (top, left):
            cell = self.cursor.get_cell()
            if not cell.selected:
                cell.toggle_selection()
                self.parent.update(self.cursor.position())
            if (top == self.selection_start[0] and \
               left >= self.selection_start[1]) or \
               top > self.selection_start[0]:
                self.cursor.advance_column(scroll=False)
            else:
                self.cursor.previous_column(scroll=False)
        cell = self.cursor.get_cell()
        if not cell.selected:
            cell.toggle_selection()
            self.parent.update(self.cursor.position())
        self.cursor.restore_row_col()

    def get_selection_text(self):
        text = ""
        buf = self.get_buffer()
        cell = None
        for row in xrange(0, len(buf)):
            for col in xrange(0, len(buf[row])):
                cell = buf[row][col]
                if cell.selected:
                    text += cell.ch
            if cell and cell.selected and not cell.has_data:
                text += '\n'
        return text

    def find_word(self, top, left):
        word_separators = [' ', ':', '.', ',', '[', ']', '(', ')', '<', 
                           '>', '!', '`']
        cell = self.get_cell(top, left)
        first = (top, left)
        last = (top, left)
        if cell.ch in word_separators:
            return (first, last)

        (row, col) = (top, left)
        is_eol = not self.get_cell(row, col).has_data
        while first > (self.base, 0):
            col -= 1
            if col < 0:
                col = self.width - 1
                row -= 1
            if row < self.base:
                row = self.base
                col = 0
                break
            cell = self.get_cell(row, col)
            if not is_eol and not cell.has_data:
                col = 0
                row += 1
                break
            if cell.ch in word_separators:
                col += 1
                break
            while not cell.has_data and col > 0:
                is_eol = True
                col -= 1
                cell = self.get_cell(row, col)
                if cell.has_data:
                    col += 1
            if is_eol:
                break
        first = (row, col)

        (row, col) = (top, left)
        is_eol = not self.get_cell(row, col).has_data
        while last < (self.base + self.height - 1, self.width - 1):
            col += 1
            cell = self.get_cell(row, col)
            if cell.ch in word_separators:
                col -= 1
                break
            if not cell.has_data and not is_eol:
                col -= 1
                break
            while not cell.has_data and col < self.width - 1:
                col += 1
                cell = self.get_cell(row, col)
            if not cell.has_data:
                break
        last = (row, col)
        return (first, last)