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 __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()
def save_cursor(self): self.saved_cursor = self.cursor # DECSC self.cursor = TerminalCursor(self, self.font_name, self.font_size)
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)