📄 widget.py
字号:
"""Set the part of the frame that is in focus. part -- 'header', 'footer' or 'body' """ assert part in ('header', 'footer', 'body') self.focus_part = part self._invalidate() def frame_top_bottom(self, (maxcol,maxrow), focus): """Calculate the number of rows for the header and footer. Returns (head rows, foot rows),(orig head, orig foot). orig head/foot are from rows() calls. """ frows = hrows = 0 if self.header: hrows = self.header.rows((maxcol,), self.focus_part=='header' and focus) if self.footer: frows = self.footer.rows((maxcol,), self.focus_part=='footer' and focus) remaining = maxrow if self.focus_part == 'footer': if frows >= remaining: return (0, remaining),(hrows, frows) remaining -= frows if hrows >= remaining: return (remaining, frows),(hrows, frows) elif self.focus_part == 'header': if hrows >= maxrow: return (remaining, 0),(hrows, frows) remaining -= hrows if frows >= remaining: return (hrows, remaining),(hrows, frows) elif hrows + frows >= remaining: # self.focus_part == 'body' rless1 = max(0, remaining-1) if frows >= remaining-1: return (0, rless1),(hrows, frows) remaining -= frows rless1 = max(0, remaining-1) return (rless1,frows),(hrows, frows) return (hrows, frows),(hrows, frows) def render(self, (maxcol,maxrow), focus=False): """Render frame and return it.""" (htrim, ftrim),(hrows, frows) = self.frame_top_bottom( (maxcol, maxrow), focus) combinelist = [] depends_on = [] head = None if htrim and htrim < hrows: head = Filler(self.header, 'top').render( (maxcol, htrim), focus and self.focus_part == 'header') elif htrim: head = self.header.render((maxcol,), focus and self.focus_part == 'header') assert head.rows() == hrows, "rows, render mismatch" if head: combinelist.append((head, 'header', self.focus_part == 'header')) depends_on.append(self.header) if ftrim+htrim < maxrow: body = self.body.render((maxcol, maxrow-ftrim-htrim), focus and self.focus_part == 'body') combinelist.append((body, 'body', self.focus_part == 'body')) depends_on.append(self.body) foot = None if ftrim and ftrim < frows: foot = Filler(self.footer, 'bottom').render( (maxcol, ftrim), focus and self.focus_part == 'footer') elif ftrim: foot = self.footer.render((maxcol,), focus and self.focus_part == 'footer') assert foot.rows() == frows, "rows, render mismatch" if foot: combinelist.append((foot, 'footer', self.focus_part == 'footer')) depends_on.append(self.footer) return CanvasCombine(combinelist) def keypress(self, (maxcol,maxrow), key): """Pass keypress to widget in focus.""" if self.focus_part == 'header' and self.header is not None: if not self.header.selectable(): return key return self.header.keypress((maxcol,),key) if self.focus_part == 'footer' and self.footer is not None: if not self.footer.selectable(): return key return self.footer.keypress((maxcol,),key) if self.focus_part != 'body': return key remaining = maxrow if self.header is not None: remaining -= self.header.rows((maxcol,)) if self.footer is not None: remaining -= self.footer.rows((maxcol,)) if remaining <= 0: return key if not self.body.selectable(): return key return self.body.keypress( (maxcol, remaining), key ) def mouse_event(self, (maxcol, maxrow), event, button, col, row, focus): """ Pass mouse event to appropriate part of frame. Focus may be changed on button 1 press. """ (htrim, ftrim),(hrows, frows) = self.frame_top_bottom( (maxcol, maxrow), focus) if row < htrim: # within header focus = focus and self.focus_part == 'header' if is_mouse_press(event) and button==1: if self.header.selectable(): self.set_focus('header') if not hasattr(self.header, 'mouse_event'): return False return self.header.mouse_event( (maxcol,), event, button, col, row, focus ) if row >= maxrow-ftrim: # within footer focus = focus and self.focus_part == 'footer' if is_mouse_press(event) and button==1: if self.footer.selectable(): self.set_focus('footer') if not hasattr(self.footer, 'mouse_event'): return False return self.footer.mouse_event( (maxcol,), event, button, col, row-maxrow+frows, focus ) # within body focus = focus and self.focus_part == 'body' if is_mouse_press(event) and button==1: if self.body.selectable(): self.set_focus('body') if not hasattr(self.body, 'mouse_event'): return False return self.body.mouse_event( (maxcol, maxrow-htrim-ftrim), event, button, col, row-htrim, focus ) class AttrWrap(Widget): """ AttrWrap is a decorator that changes the default attribute for a FlowWidget or BoxWidget """ def __init__(self, w, attr, focus_attr = None): """ w -- widget to wrap attr -- attribute to apply to w focus_attr -- attribute to apply when in focus, if None use attr This object will pass all function calls and variable references to the wrapped widget. """ self._w = w self._attr = attr self._focus_attr = focus_attr def get_w(self): return self._w def set_w(self, w): self._w = w self._invalidate() w = property(get_w, set_w) def get_attr(self): return self._attr def set_attr(self, attr): self._attr = attr self._invalidate() attr = property(get_attr, set_attr) def get_focus_attr(self): return self._focus_attr def set_focus_attr(self, focus_attr): self._focus_attr = focus_attr self._invalidate() focus_attr = property(get_focus_attr, set_focus_attr) def render(self, size, focus = False ): """Render self.w and apply attribute. Return canvas. size -- (maxcol,) if self.w contains a flow widget or (maxcol, maxrow) if it contains a box widget. """ attr = self.attr if focus and self.focus_attr is not None: attr = self.focus_attr canv = self.w.render(size, focus=focus) canv = CompositeCanvas(canv) canv.fill_attr(attr) return canv def selectable(self): return self.w.selectable() def __getattr__(self,name): """Call getattr on wrapped widget.""" return getattr(self.w, name)class PileError(Exception): pass class Pile(Widget): # either FlowWidget or BoxWidget def __init__(self, widget_list, focus_item=None): """ widget_list -- list of widgets focus_item -- widget or integer index, if None the first selectable widget will be chosen. widget_list may also contain tuples such as: ('flow', widget) always treat widget as a flow widget ('fixed', height, widget) give this box widget a fixed height ('weight', weight, widget) if the pile is treated as a box widget then treat widget as a box widget with a height based on its relative weight value, otherwise treat widget as a flow widget widgets not in a tuple are the same as ('weight', 1, widget) If the pile is treated as a box widget there must be at least one 'weight' tuple in widget_list. """ self.__super.__init__() self.widget_list = MonitoredList(widget_list) self.item_types = [] for i in range(len(widget_list)): w = widget_list[i] if type(w) != type(()): self.item_types.append(('weight',1)) elif w[0] == 'flow': f, widget = w self.widget_list[i] = widget self.item_types.append((f,None)) w = widget elif w[0] in ('fixed', 'weight'): f, height, widget = w self.widget_list[i] = widget self.item_types.append((f,height)) w = widget else: raise PileError, "widget list item invalid %s" % `w` if focus_item is None and w.selectable(): focus_item = i self.widget_list.set_modified_callback(self._invalidate) if focus_item is None: focus_item = 0 self.set_focus(focus_item) self.pref_col = 0 def selectable(self): """Return True if the focus item is selectable.""" return self.focus_item.selectable() def set_focus(self, item): """Set the item in focus. item -- widget or integer index""" if type(item) == type(0): assert item>=0 and item<len(self.widget_list) self.focus_item = self.widget_list[item] else: assert item in self.widget_list self.focus_item = item self._invalidate() def get_focus(self): """Return the widget in focus.""" return self.focus_item def get_pref_col(self, size): """Return the preferred column for the cursor, or None.""" if not self.selectable(): return None self._update_pref_col_from_focus(size) return self.pref_col def get_item_size(self, size, i, focus, item_rows=None): """ Return a size appropriate for passing to self.widget_list[i] """ maxcol = size[0] f, height = self.item_types[i] if f=='fixed': return (maxcol, height) elif f=='weight' and len(size)==2: if not item_rows: item_rows = self.get_item_rows(size, focus) return (maxcol, item_rows[i]) else: return (maxcol,) def get_item_rows(self, size, focus): """ Return a list of the number of rows used by each widget in self.item_list. """ remaining = None maxcol = size[0] if len(size)==2: remaining = size[1] l = [] if remaining is None: # pile is a flow widget for (f, height), w in zip( self.item_types, self.widget_list): if f == 'fixed': l.append( height ) else: l.append( w.rows( (maxcol,), focus=focus and self.focus_item == w )) return l # pile is a box widget # do an extra pass to calculate rows for each widget wtotal = 0 for (f, height), w in zip(self.item_types, self.widget_list): if f == 'flow': rows = w.rows((maxcol,), focus=focus and self.focus_item == w ) l.append(rows) remaining -= rows elif f == 'fixed': l.append(height) remaining -= height else: l.append(None) wtotal += height if wtotal == 0: raise PileError, "No weighted widgets found for Pile treated as a box widget" if remaining < 0: remaining = 0 i = 0 for (f, height), li in zip(self.item_types, l): if li is None: rows = int(float(remaining)*height /wtotal+0.5) l[i] = rows remaining -= rows wtotal -= height i += 1 return l def render(self, size, focus=False): """ Render all widgets in self.widget_list and return the results stacked one on top of the next. """ maxcol = size[0] item_rows = None combinelist = [] i = 0 for (f, height), w in zip(self.item_types, self.widget_list): item_focus = self.focus_item == w canv = None if f == 'fixed': canv = w.render( (maxcol, height), focus=focus and item_focus) elif f == 'flow' or len(size)==1: canv = w.render( (maxcol,), focus=focus and item_focus) else: if item_rows is None: item_rows = self.get_item_rows(size, focus) rows = item_rows[i] if rows>0: canv = w.render( (maxcol, rows), focus=focus and item_focus ) if canv: combinelist.append((canv, i, item_focus)) i+=1 return CanvasCombine(combinelist) def get_cursor_coords(self, size): """Return the cursor coordinates of the focus widget.""" if not self.focus_item.selectable(): return None if not hasattr(self.focus_item,'get_cursor_coords'): return None i = self.widget_list.index(self.focus_item) f, height = self.item_types[i] item_rows = None maxcol = size[0] if f == 'fixed' or (f=='weight' and len(size)==2): if f == 'fixed': maxrow = height else: if item_rows is None: item_rows = self.get_item_rows(size, focus=True) maxrow = item_rows[i] coords = self.focus_item.get_cursor_coords( (maxcol,maxrow)) else: coords = self.focus_item.get_cursor_coords((maxcol,)) if coords is None: return None x,y = coords if i > 0: if item_rows is None: item_rows = self.get_item_rows(size, focus=True) for r in item_rows[:i]: y += r return x, y def rows(self, (maxcol,), focus=False ): """Return the number of rows required for this widget.""" return sum( self.get_item_rows( (maxcol,), focus ) ) def keypress(self, size, key ): """Pass the keypress to the widget in focus. Unhandled 'up' and 'down' keys may cause a focus change.""" maxcol = size[0] item_rows = None if len(size)==2: item_rows = self.get_item_rows( size, focus=True ) i = self.widget_list.index(self.focus_item) f, height = self.item_types[i] if self.focus_item.selectable(): tsize = self.get_item_size(size,i,True,item_rows) key = self.focus_item.keypress( tsize, key ) if key not in ('up', 'down'): return key if key == 'up': candidates = range(i-1, -1, -1) # count backwards to 0 else: # key == 'down' candidates = range(i
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -