?? widget.py
字號:
#!/usr/bin/python## Urwid basic widget classes# Copyright (C) 2004-2007 Ian Ward## This library is free software; you can redistribute it and/or# modify it under the terms of the GNU Lesser General Public# License as published by the Free Software Foundation; either# version 2.1 of the License, or (at your option) any later version.## This library is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU# Lesser General Public License for more details.## You should have received a copy of the GNU Lesser General Public# License along with this library; if not, write to the Free Software# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA## Urwid web site: http://excess.org/urwid/import stringfrom util import *from canvas import *try: sum # old python?except: sum = lambda l: reduce(lambda a,b: a+b, l, 0)class WidgetMeta(MetaSuper, MetaSignals): """ Automatic caching of render and rows methods. Class variable no_cache is a list of names of methods to not cache. Class variable ignore_focus if defined and True indicates that this widget is not affected by the focus parameter, so it may be ignored when caching. """ def __init__(cls, name, bases, d): no_cache = d.get("no_cache", []) super(WidgetMeta, cls).__init__(name, bases, d) if "render" in d: if "render" not in no_cache: render_fn = cache_widget_render(cls) else: render_fn = nocache_widget_render(cls) cls.render = render_fn if "rows" in d and "rows" not in no_cache: cls.rows = cache_widget_rows(cls) if "no_cache" in d: del cls.no_cache if "ignore_focus" in d: del cls.ignore_focusclass WidgetError(Exception): passdef validate_size(widget, size, canv): """ Raise a WidgetError if a canv does not match size size. """ if (size and size[1:] != (0,) and size[0] != canv.cols()) or \ (len(size)>1 and size[1] != canv.rows()): raise WidgetError("Widget %r rendered (%d x %d) canvas" " when passed size %r!" % (widget, canv.cols(), canv.rows(), size))def cache_widget_render(cls): """ Return a function that wraps the cls.render() method and fetches and stores canvases with CanvasCache. """ ignore_focus = bool(getattr(cls, "ignore_focus", False)) fn = cls.render def cached_render(self, size, focus=False): focus = focus and not ignore_focus canv = CanvasCache.fetch(self, cls, size, focus) if canv: return canv canv = fn(self, size, focus=focus) validate_size(self, size, canv) if canv.widget_info: canv = CompositeCanvas(canv) canv.finalize(self, size, focus) CanvasCache.store(cls, canv) return canv cached_render.original_fn = fn return cached_renderdef nocache_widget_render(cls): """ Return a function that wraps the cls.render() method and finalizes the canvas that it returns. """ fn = cls.render if hasattr(fn, "original_fn"): fn = fn.original_fn def finalize_render(self, size, focus=False): canv = fn(self, size, focus=focus) if canv.widget_info: canv = CompositeCanvas(canv) validate_size(self, size, canv) canv.finalize(self, size, focus) return canv finalize_render.original_fn = fn return finalize_renderdef nocache_widget_render_instance(self): """ Return a function that wraps the cls.render() method and finalizes the canvas that it returns, but does not cache the canvas. """ fn = self.render.original_fn def finalize_render(size, focus=False): canv = fn(self, size, focus=focus) if canv.widget_info: canv = CompositeCanvas(canv) canv.finalize(self, size, focus) return canv finalize_render.original_fn = fn return finalize_renderdef cache_widget_rows(cls): """ Return a function that wraps the cls.rows() method and returns rows from the CanvasCache if available. """ ignore_focus = bool(getattr(cls, "ignore_focus", False)) fn = cls.rows def cached_rows(self, size, focus=False): focus = focus and not ignore_focus canv = CanvasCache.fetch(self, cls, size, focus) if canv: return canv.rows() return fn(self, size, focus) return cached_rowsclass Widget(object): """ base class of widgets """ __metaclass__ = WidgetMeta _selectable = False def _invalidate(self): CanvasCache.invalidate(self) def _emit(self, name, *args): Signals.emit(self, name, *args) def selectable(self): return self._selectable class FlowWidget(Widget): """ base class of widgets """ def rows(self, (maxcol,), focus=False): """ All flow widgets must implement this function. """ raise NotImplementedError() def render(self, (maxcol,), focus=False): """ All widgets must implement this function. """ raise NotImplementedError()class BoxWidget(Widget): """ base class of width and height constrained widgets such as the top level widget attached to the display object """ _selectable = True def render(self, size, focus=False): """ All widgets must implement this function. """ raise NotImplementedError() def fixed_size(size): """ raise ValueError if size != (). Used by FixedWidgets to test size parameter. """ if size != (): raise ValueError("FixedWidget takes only () for size." \ "passed: %s" % `size`)class FixedWidget(Widget): """ base class of widgets that know their width and height and cannot be resized """ def render(self, size, focus=False): """ All widgets must implement this function. """ raise NotImplementedError() def pack(self, size=None, focus=False): """ All fixed widgets must implement this function. """ raise NotImplementedError()class Divider(FlowWidget): """ Horizontal divider widget """ ignore_focus = True def __init__(self,div_char=" ",top=0,bottom=0): """ div_char -- character to repeat across line top -- number of blank lines above bottom -- number of blank lines below """ self.__super.__init__() self.div_char = div_char self.top = top self.bottom = bottom def rows(self, (maxcol,), focus=False): """Return the number of lines that will be rendered.""" return self.top + 1 + self.bottom def render(self, (maxcol,), focus=False): """Render the divider as a canvas and return it.""" canv = SolidCanvas(self.div_char, maxcol, 1) canv = CompositeCanvas(canv) if self.top or self.bottom: canv.pad_trim_top_bottom(self.top, self.bottom) return canv class SolidFill(BoxWidget): _selectable = False ignore_focus = True def __init__(self,fill_char=" "): """ fill_char -- character to fill area with """ self.__super.__init__() self.fill_char = fill_char def render(self,(maxcol,maxrow), focus=False ): """Render the Fill as a canvas and return it.""" return SolidCanvas(self.fill_char, maxcol, maxrow) class TextError(Exception): passclass Text(FlowWidget): """ a horizontally resizeable text widget """ ignore_focus = True def __init__(self,markup, align='left', wrap='space', layout=None): """ markup -- content of text widget, one of: plain string -- string is displayed ( attr, markup2 ) -- markup2 is given attribute attr [ markupA, markupB, ... ] -- list items joined together align -- align mode for text layout wrap -- wrap mode for text layout layout -- layout object to use, defaults to StandardTextLayout """ self.__super.__init__() self._cache_maxcol = None self.set_text(markup) self.set_layout(align, wrap, layout) def _invalidate(self): self._cache_maxcol = None self.__super._invalidate() def set_text(self,markup): """Set content of text widget.""" self.text, self.attrib = decompose_tagmarkup(markup) self._invalidate() def get_text(self): """ Returns (text, attributes). text -- complete string content of text widget attributes -- run length encoded attributes for text """ return self.text, self.attrib def set_align_mode(self, mode): """ Set text alignment / justification. Valid modes for StandardTextLayout are: 'left', 'center' and 'right' """ if not self.layout.supports_align_mode(mode): raise TextError("Alignment mode %s not supported."% `mode`) self.align_mode = mode self._invalidate() def set_wrap_mode(self, mode): """ Set wrap mode. Valid modes for StandardTextLayout are : 'any' : wrap at any character 'space' : wrap on space character 'clip' : truncate lines instead of wrapping """ if not self.layout.supports_wrap_mode(mode): raise TextError("Wrap mode %s not supported"%`mode`) self.wrap_mode = mode self._invalidate() def set_layout(self, align, wrap, layout=None): """ Set layout object, align and wrap modes. align -- align mode for text layout wrap -- wrap mode for text layout layout -- layout object to use, defaults to StandardTextLayout """ if layout is None: layout = default_layout self.layout = layout self.set_align_mode( align ) self.set_wrap_mode( wrap ) def render(self,(maxcol,), focus=False): """ Render contents with wrapping and alignment. Return canvas. """ text, attr = self.get_text() trans = self.get_line_translation( maxcol, (text,attr) ) return apply_text_layout(text, attr, trans, maxcol) def rows(self,(maxcol,), focus=False): """Return the number of rows the rendered text spans.""" return len(self.get_line_translation(maxcol)) def get_line_translation(self, maxcol, ta=None): """Return layout structure for mapping self.text to a canvas. """ if not self._cache_maxcol or self._cache_maxcol != maxcol: self._update_cache_translation(maxcol, ta) return self._cache_translation def _update_cache_translation(self,maxcol, ta): if ta: text, attr = ta else: text, attr = self.get_text() self._cache_maxcol = maxcol self._cache_translation = self._calc_line_translation( text, maxcol ) def _calc_line_translation(self, text, maxcol ): return self.layout.layout( text, self._cache_maxcol, self.align_mode, self.wrap_mode ) def pack(self, size=None, focus=False): """ Return the number of screen columns 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, 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,) 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,) class Edit(Text): """Text edit widget""" def valid_char(self, ch): """Return true for printable characters.""" return is_wide_char(ch,0) or (len(ch)==1 and ord(ch) >= 32) def selectable(self): return True def __init__(self, caption = "", edit_text = "", multiline = False, align = 'left', wrap = 'space', allow_tab = False, edit_pos = None, layout=None): """ caption -- markup for caption preceeding edit_text edit_text -- text string for editing multiline -- True: 'enter' inserts newline False: return it align -- align mode wrap -- wrap mode allow_tab -- True: 'tab' inserts 1-8 spaces False: return it edit_pos -- initial position for cursor, None:at end layout -- layout object """ self.__super.__init__("", align, wrap, layout) assert type(edit_text)==type("") or type(edit_text)==type(u"") self.multiline = multiline self.allow_tab = allow_tab self.edit_pos = 0 self.set_caption(caption) self.set_edit_text(edit_text) if edit_pos is None: edit_pos = len(edit_text) self.set_edit_pos(edit_pos) self._shift_view_to_cursor = False def get_text(self): """get_text() -> text, attributes text -- complete text of caption and edit_text attributes -- run length encoded attributes for text """ return self.caption + self.edit_text, self.attrib def get_pref_col(self, (maxcol,)): """Return the preferred column for the cursor, or the current cursor x value.""" pref_col, then_maxcol = self.pref_col_maxcol if then_maxcol != maxcol: return self.get_cursor_coords((maxcol,))[0] else: return pref_col def update_text(self): """Deprecated. Use set_caption and/or set_edit_text instead. Make sure any cached line translation is not reused.""" self._invalidate() def set_caption(self, caption): """Set the caption markup for this widget."""
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -