def __init__(self, text=None, attr=None, cs=None, cursor=None, maxcol=None, check_width=True): """ text -- list of strings, one for each line attr -- list of run length encoded attributes for text cs -- list of run length encoded character set for text cursor -- (x,y) of cursor or None maxcol -- screen columns taken by this canvas check_width -- check and fix width of all lines in text """ Canvas.__init__(self) if text == None: text = [] if check_width: widths = [] for t in text: if type(t) != type(""): raise CanvasError("Canvas text must be plain strings encoded in the screen's encoding", `text`) widths.append( calc_width( t, 0, len(t)) ) else: assert type(maxcol) == type(0) widths = [maxcol] * len(text) if maxcol is None: if widths: # find maxcol ourselves maxcol = max(widths) else: maxcol = 0 if attr == None: attr = [[] for x in range(len(text))] if cs == None: cs = [[] for x in range(len(text))] # pad text and attr to maxcol for i in range(len(text)): w = widths[i] if w > maxcol: raise CanvasError("Canvas text is wider than the maxcol specified \n%s\n%s\n%s"%(`maxcol`,`widths`,`text`)) if w < maxcol: text[i] = text[i] + " "*(maxcol-w) a_gap = len(text[i]) - rle_len( attr[i] ) if a_gap < 0: raise CanvasError("Attribute extends beyond text \n%s\n%s" % (`text[i]`,`attr[i]`) ) if a_gap: rle_append_modify( attr[i], (None, a_gap)) cs_gap = len(text[i]) - rle_len( cs[i] ) if cs_gap < 0: raise CanvasError("Character Set extends beyond text \n%s\n%s" % (`text[i]`,`cs[i]`) ) if cs_gap: rle_append_modify( cs[i], (None, cs_gap)) self._attr = attr self._cs = cs self.cursor = cursor self._text = text self._maxcol = maxcol
def calc_coords( text, layout, pos, clamp=1 ): """ Calculate the coordinates closest to position pos in text with layout. text -- raw string or unicode string layout -- layout structure applied to text pos -- integer position into text clamp -- ignored right now """ closest = None y = 0 for line_layout in layout: x = 0 for seg in line_layout: s = LayoutSegment(seg) if s.offs is None: x += s.sc continue if s.offs == pos: return x,y if s.end is not None and s.offs<=pos and s.end>pos: x += calc_width( text, s.offs, pos ) return x,y distance = abs(s.offs - pos) if s.end is not None and s.end<pos: distance = pos - (s.end-1) if closest is None or distance < closest[0]: closest = distance, (x,y) x += s.sc y += 1 if closest: return closest[1] return 0,0
def _last_row(self, row): """On the last row we need to slide the bottom right character into place. Calculate the new line, attr and an insert sequence to do that. eg. last row: XXXXXXXXXXXXXXXXXXXXYZ Y will be drawn after Z, shifting Z into position. """ new_row = row[:-1] z_attr, z_cs, last_text = row[-1] last_cols = util.calc_width(last_text, 0, len(last_text)) last_offs, z_col = util.calc_text_pos(last_text, 0, len(last_text), last_cols-1) if last_offs == 0: z_text = last_text del new_row[-1] # we need another segment y_attr, y_cs, nlast_text = row[-2] nlast_cols = util.calc_width(nlast_text, 0, len(nlast_text)) z_col += nlast_cols nlast_offs, y_col = util.calc_text_pos(nlast_text, 0, len(nlast_text), nlast_cols-1) y_text = nlast_text[nlast_offs:] if nlast_offs: new_row.append((y_attr, y_cs, nlast_text[:nlast_offs])) else: z_text = last_text[last_offs:] y_attr, y_cs = z_attr, z_cs nlast_cols = util.calc_width(last_text, 0, last_offs) nlast_offs, y_col = util.calc_text_pos(last_text, 0, last_offs, nlast_cols-1) y_text = last_text[nlast_offs:last_offs] if nlast_offs: new_row.append((y_attr, y_cs, last_text[:nlast_offs])) new_row.append((z_attr, z_cs, z_text)) return new_row, z_col-y_col, (y_attr, y_cs, y_text)
def pack(self, size=None, focus=False): """ Return the number of screen columns and rows required for this Text widget to be displayed without wrapping or clipping, as a single element tuple. size -- None for unlimited screen columns or (maxcol,) to specify a maximum column size >>> Text("important things").pack() (16, 1) >>> Text("important things").pack((15,)) (9, 2) >>> Text("important things").pack((8,)) (8, 2) """ text, attr = self.get_text() if size is not None: (maxcol,) = size if not hasattr(self.layout, "pack"): return size trans = self.get_line_translation( maxcol, (text,attr)) cols = self.layout.pack( maxcol, trans ) return (cols, len(trans)) i = 0 cols = 0 while i < len(text): j = text.find('\n', i) if j == -1: j = len(text) c = calc_width(text, i, j) if c>cols: cols = c i = j+1 return (cols, text.count('\n') + 1)
if y in old_line_numbers: old_line = y else: old_line = old_line_numbers[0] send( "<%d\n"%old_line ) continue col = 0 for (a, cs, run) in row: run = run.translate(_trans_table) if a is None: fg,bg,mono = "black", "light gray", None else: fg,bg,mono = self.palette[a] if y == cy and col <= cx: run_width = util.calc_width(run, 0, len(run)) if col+run_width > cx: l.append(code_span(run, fg, bg, cx-col)) else: l.append(code_span(run, fg, bg)) col += run_width else: l.append(code_span(run, fg, bg)) send("".join(l)+"\n") self.last_screen = new_screen self.last_screen_width = cols if self.update_method == "polling": sys.stdout.write("".join(sendq))
def calculate_text_segments( self, text, width, wrap ): """ Calculate the segments of text to display given width screen columns to display them. text - text to display width - number of available screen columns wrap - wrapping mode used Returns a layout structure without aligmnent applied. """ b = [] p = 0 if wrap == 'clip': # no wrapping to calculate, so it's easy. while p<=len(text): n_cr = text.find("\n", p) if n_cr == -1: n_cr = len(text) sc = calc_width(text, p, n_cr) l = [(0,n_cr)] if p!=n_cr: l = [(sc, p, n_cr)] + l b.append(l) p = n_cr+1 return b while p<=len(text): # look for next eligible line break n_cr = text.find("\n", p) if n_cr == -1: n_cr = len(text) sc = calc_width(text, p, n_cr) if sc == 0: # removed character hint b.append([(0,n_cr)]) p = n_cr+1 continue if sc <= width: # this segment fits b.append([(sc,p,n_cr), # removed character hint (0,n_cr)]) p = n_cr+1 continue pos, sc = calc_text_pos( text, p, n_cr, width ) # FIXME: handle pathological width=1 double-byte case if wrap == 'any': b.append([(sc,p,pos)]) p = pos continue assert wrap == 'space' if text[pos] == " ": # perfect space wrap b.append([(sc,p,pos), # removed character hint (0,pos)]) p = pos+1 continue if is_wide_char(text, pos): # perfect next wide b.append([(sc,p,pos)]) p = pos continue prev = pos while prev > p: prev = move_prev_char(text, p, prev) if text[prev] == " ": sc = calc_width(text,p,prev) l = [(0,prev)] if p!=prev: l = [(sc,p,prev)] + l b.append(l) p = prev+1 break if is_wide_char(text,prev): # wrap after wide char next = move_next_char(text, prev, pos) sc = calc_width(text,p,next) b.append([(sc,p,next)]) p = next break else: # unwrap previous line space if possible to # fit more text (we're breaking a word anyway) if b and (len(b[-1]) == 2 or ( len(b[-1])==1 and len(b[-1][0])==2 )): # look for removed space above if len(b[-1]) == 1: [(h_sc, h_off)] = b[-1] p_sc = 0 p_off = p_end = h_off else: [(p_sc, p_off, p_end), (h_sc, h_off)] = b[-1] if (p_sc < width and h_sc==0 and text[h_off] == " "): # combine with previous line del b[-1] p = p_off pos, sc = calc_text_pos( text, p, n_cr, width ) b.append([(sc,p,pos)]) # check for trailing " " or "\n" p = pos if p < len(text) and ( text[p] in (" ","\n")): # removed character hint b[-1].append((0,p)) p += 1 continue # force any char wrap b.append([(sc,p,pos)]) p = pos return b
class HtmlGenerator: # class variables fragments = [] sizes = [] keys = [] started = True def __init__(self): self.palette = {} self.has_color = True def register_palette(self, l): """Register a list of palette entries. l -- list of (name, foreground, background) or (name, same_as_other_name) palette entries. calls self.register_palette_entry for each item in l """ for item in l: if len(item) in (3, 4): self.register_palette_entry(*item) continue assert len(item) == 2, "Invalid register_palette usage" name, like_name = item if not self.palette.has_key(like_name): raise Exception("palette entry '%s' doesn't exist" % like_name) self.palette[name] = self.palette[like_name] def register_palette_entry(self, name, foreground, background, mono=None): """Register a single palette entry. name -- new entry/attribute name foreground -- foreground colour background -- background colour mono -- monochrome terminal attribute See curses_display.register_palette_entry for more info. """ if foreground == "default": foreground = "black" if background == "default": background = "light gray" self.palette[name] = (foreground, background, mono) def set_mouse_tracking(self): """Not yet implemented""" pass def start(self): pass def stop(self): pass def run_wrapper(self, fn): """Call fn.""" return fn() def draw_screen(self, (cols, rows), r): """Create an html fragment from the render object. Append it to HtmlGenerator.fragments list. """ # collect output in l l = [] assert r.rows() == rows if r.cursor is not None: cx, cy = r.cursor else: cx = cy = None y = -1 for row in r.content(): y += 1 col = 0 for a, cs, run in row: run = run.translate(_trans_table) if a is None: fg, bg, mono = "black", "light gray", None else: fg, bg, mono = self.palette[a] if y == cy and col <= cx: run_width = util.calc_width(run, 0, len(run)) if col + run_width > cx: l.append(html_span(run, fg, bg, cx - col)) else: l.append(html_span(run, fg, bg)) col += run_width else: l.append(html_span(run, fg, bg)) l.append("\n") # add the fragment to the list self.fragments.append("<pre>%s</pre>" % "".join(l))
class HtmlGenerator(BaseScreen): # class variables fragments = [] sizes = [] keys = [] started = True def __init__(self): super(HtmlGenerator, self).__init__() self.colors = 16 self.bright_is_bold = False # ignored self.has_underline = True # ignored self.register_palette_entry(None, _default_foreground, _default_background) def set_terminal_properties(self, colors=None, bright_is_bold=None, has_underline=None): if colors is None: colors = self.colors if bright_is_bold is None: bright_is_bold = self.bright_is_bold if has_underline is None: has_unerline = self.has_underline self.colors = colors self.bright_is_bold = bright_is_bold self.has_underline = has_underline def set_mouse_tracking(self): """Not yet implemented""" pass def start(self): pass def stop(self): pass def set_input_timeouts(self, *args): pass def reset_default_terminal_palette(self, *args): pass def run_wrapper(self, fn): """Call fn.""" return fn() def draw_screen(self, (cols, rows), r): """Create an html fragment from the render object. Append it to HtmlGenerator.fragments list. """ # collect output in l l = [] assert r.rows() == rows if r.cursor is not None: cx, cy = r.cursor else: cx = cy = None y = -1 for row in r.content(): y += 1 col = 0 for a, cs, run in row: run = run.translate(_trans_table) if isinstance(a, AttrSpec): aspec = a else: aspec = self._palette[a][{ 1: 1, 16: 0, 88: 2, 256: 3 }[self.colors]] if y == cy and col <= cx: run_width = util.calc_width(run, 0, len(run)) if col + run_width > cx: l.append(html_span(run, aspec, cx - col)) else: l.append(html_span(run, aspec)) col += run_width else: l.append(html_span(run, aspec)) l.append("\n") # add the fragment to the list self.fragments.append("<pre>%s</pre>" % "".join(l))