📄 listbox.py
字号:
trim_top, fill_above = top trim_bottom, fill_below = bottom if focus_widget.selectable(): return if trim_bottom: fill_below = fill_below[:-1] new_row_offset = row_offset + focus_rows for widget, pos, rows in fill_below: if widget.selectable(): self.body.set_focus(pos) self.shift_focus((maxcol, maxrow), new_row_offset) return new_row_offset += rows def _set_focus_complete(self, (maxcol, maxrow), focus): """ Finish setting the position now that we have maxcol & maxrow. """ self._invalidate() if self.set_focus_pending == "first selectable": return self._set_focus_first_selectable( (maxcol,maxrow), focus) if self.set_focus_valign_pending is not None: return self._set_focus_valign_complete( (maxcol,maxrow), focus) coming_from, focus_widget, focus_pos = self.set_focus_pending self.set_focus_pending = None # new position new_focus_widget, position = self.body.get_focus() if focus_pos == position: # do nothing return # restore old focus temporarily self.body.set_focus(focus_pos) middle,top,bottom=self.calculate_visible((maxcol,maxrow),focus) focus_offset, focus_widget, focus_pos, focus_rows, cursor=middle trim_top, fill_above = top trim_bottom, fill_below = bottom offset = focus_offset for widget, pos, rows in fill_above: offset -= rows if pos == position: self.change_focus((maxcol, maxrow), pos, offset, 'below' ) return offset = focus_offset + focus_rows for widget, pos, rows in fill_below: if pos == position: self.change_focus((maxcol, maxrow), pos, offset, 'above' ) return offset += rows # failed to find widget among visible widgets self.body.set_focus( position ) widget, position = self.body.get_focus() rows = widget.rows((maxcol,), focus) if coming_from=='below': offset = 0 elif coming_from=='above': offset = maxrow-rows else: offset = (maxrow-rows)/2 self.shift_focus((maxcol, maxrow), offset) def shift_focus(self, (maxcol,maxrow), offset_inset ): """Move the location of the current focus relative to the top. offset_inset -- either the number of rows between the top of the listbox and the start of the focus widget (+ve value) or the number of lines of the focus widget hidden off the top edge of the listbox (-ve value) or 0 if the top edge of the focus widget is aligned with the top edge of the listbox """ if offset_inset >= 0: if offset_inset >= maxrow: raise ListBoxError, "Invalid offset_inset: %s, only %s rows in list box"% (`offset_inset`, `maxrow`) self.offset_rows = offset_inset self.inset_fraction = (0,1) else: target, _ignore = self.body.get_focus() tgt_rows = target.rows( (maxcol,), focus=1 ) if offset_inset + tgt_rows <= 0: raise ListBoxError, "Invalid offset_inset: %s, only %s rows in target!" %(`offset_inset`, `tgt_rows`) self.offset_rows = 0 self.inset_fraction = (-offset_inset,tgt_rows) self._invalidate() def update_pref_col_from_focus(self, (maxcol,maxrow) ): """Update self.pref_col from the focus widget.""" widget, old_pos = self.body.get_focus() if widget is None: return pref_col = None if hasattr(widget,'get_pref_col'): pref_col = widget.get_pref_col((maxcol,)) if pref_col is None and hasattr(widget,'get_cursor_coords'): coords = widget.get_cursor_coords((maxcol,)) if type(coords) == type(()): pref_col,y = coords if pref_col is not None: self.pref_col = pref_col def change_focus(self, (maxcol,maxrow), position, offset_inset = 0, coming_from = None, cursor_coords = None, snap_rows = None ): """Change the current focus widget. position -- a position compatible with self.body.set_focus offset_inset_rows -- either the number of rows between the top of the listbox and the start of the focus widget (+ve value) or the number of lines of the focus widget hidden off the top edge of the listbox (-ve value) or 0 if the top edge of the focus widget is aligned with the top edge of the listbox (default if unspecified) coming_from -- eiter 'above', 'below' or unspecified (None) cursor_coords -- (x, y) tuple indicating the desired column and row for the cursor, a (x,) tuple indicating only the column for the cursor, or unspecified (None) snap_rows -- the maximum number of extra rows to scroll when trying to "snap" a selectable focus into the view """ # update pref_col before change if cursor_coords: self.pref_col = cursor_coords[0] else: self.update_pref_col_from_focus((maxcol,maxrow)) self.body.set_focus(position) target, _ignore = self.body.get_focus() tgt_rows = target.rows( (maxcol,), focus=1 ) if snap_rows is None: snap_rows = maxrow-1 # "snap" to selectable widgets align_top = 0 align_bottom = maxrow - tgt_rows if ( coming_from == 'above' and target.selectable() and offset_inset > align_bottom and align_bottom >= offset_inset-snap_rows ): offset_inset = align_bottom if ( coming_from == 'below' and target.selectable() and offset_inset < align_top and align_top <= offset_inset+snap_rows ): offset_inset = align_top # convert offset_inset to offset_rows or inset_fraction if offset_inset >= 0: self.offset_rows = offset_inset self.inset_fraction = (0,1) else: if offset_inset + tgt_rows <= 0: raise ListBoxError, "Invalid offset_inset: %s, only %s rows in target!" %(offset_inset, tgt_rows) self.offset_rows = 0 self.inset_fraction = (-offset_inset,tgt_rows) if cursor_coords is None: if coming_from is None: return # must either know row or coming_from cursor_coords = (self.pref_col,) if not hasattr(target,'move_cursor_to_coords'): return attempt_rows = [] if len(cursor_coords) == 1: # only column (not row) specified # start from closest edge and move inwards (pref_col,) = cursor_coords if coming_from=='above': attempt_rows = range( 0, tgt_rows ) else: assert coming_from == 'below', "must specify coming_from ('above' or 'below') if cursor row is not specified" attempt_rows = range( tgt_rows, -1, -1) else: # both column and row specified # start from preferred row and move back to closest edge (pref_col, pref_row) = cursor_coords if pref_row < 0 or pref_row >= tgt_rows: raise ListBoxError, "cursor_coords row outside valid range for target. pref_row:%s target_rows:%s"%(`pref_row`,`tgt_rows`) if coming_from=='above': attempt_rows = range( pref_row, -1, -1 ) elif coming_from=='below': attempt_rows = range( pref_row, tgt_rows ) else: attempt_rows = [pref_row] for row in attempt_rows: if target.move_cursor_to_coords((maxcol,),pref_col,row): break self._invalidate() def get_focus_offset_inset(self,(maxcol, maxrow)): """Return (offset rows, inset rows) for focus widget.""" focus_widget, pos = self.body.get_focus() focus_rows = focus_widget.rows((maxcol,), True) offset_rows = self.offset_rows inset_rows = 0 if offset_rows == 0: inum, iden = self.inset_fraction if inum < 0 or iden < 0 or inum >= iden: raise ListBoxError, "Invalid inset_fraction: %s"%`self.inset_fraction` inset_rows = focus_rows * inum / iden assert inset_rows < focus_rows, "urwid inset_fraction error (please report)" return offset_rows, inset_rows def make_cursor_visible(self,(maxcol,maxrow)): """Shift the focus widget so that its cursor is visible.""" focus_widget, pos = self.body.get_focus() if focus_widget is None: return if not focus_widget.selectable(): return if not hasattr(focus_widget,'get_cursor_coords'): return cursor = focus_widget.get_cursor_coords((maxcol,)) if cursor is None: return cx, cy = cursor offset_rows, inset_rows = self.get_focus_offset_inset( (maxcol, maxrow)) if cy < inset_rows: self.shift_focus( (maxcol,maxrow), - (cy) ) return if offset_rows - inset_rows + cy >= maxrow: self.shift_focus( (maxcol,maxrow), maxrow-cy-1 ) return def keypress(self,(maxcol,maxrow), key): """Move selection through the list elements scrolling when necessary. 'up' and 'down' are first passed to widget in focus in case that widget can handle them. 'page up' and 'page down' are always handled by the ListBox. Keystrokes handled by this widget are: 'up' up one line (or widget) 'down' down one line (or widget) 'page up' move cursor up one listbox length 'page down' move cursor down one listbox length """ if self.set_focus_pending or self.set_focus_valign_pending: self._set_focus_complete( (maxcol,maxrow), focus=True ) focus_widget, pos = self.body.get_focus() if focus_widget is None: # empty listbox, can't do anything return key if key not in ['page up','page down']: if focus_widget.selectable(): key = focus_widget.keypress((maxcol,),key) if key is None: self.make_cursor_visible((maxcol,maxrow)) return # pass off the heavy lifting if key == 'up': return self._keypress_up((maxcol, maxrow)) if key == 'down': return self._keypress_down((maxcol, maxrow)) if key == 'page up': return self._keypress_page_up((maxcol, maxrow)) if key == 'page down': return self._keypress_page_down((maxcol, maxrow)) return key def _keypress_up( self, (maxcol, maxrow) ): middle, top, bottom = self.calculate_visible( (maxcol,maxrow), focus=1 ) if middle is None: return 'up' focus_row_offset,focus_widget,focus_pos,_ignore,cursor = middle trim_top, fill_above = top row_offset = focus_row_offset # look for selectable widget above pos = focus_pos widget = None for widget, pos, rows in fill_above: row_offset -= rows if widget.selectable(): # this one will do self.change_focus((maxcol,maxrow), pos, row_offset, 'below') return # at this point we must scroll row_offset += 1 self._invalidate() if row_offset > 0: # need to scroll in another candidate widget widget, pos = self.body.get_prev(pos) if widget is None: # cannot scroll any further return 'up' # keypress not handled rows = widget.rows((maxcol,), focus=1) row_offset -= rows if widget.selectable(): # this one will do self.change_focus((maxcol,maxrow), pos, row_offset, 'below') return if not focus_widget.selectable() or focus_row_offset+1>=maxrow: # just take top one if focus is not selectable # or if focus has moved out of view if widget is None: self.shift_focus((maxcol,maxrow), row_offset) return self.change_focus((maxcol,maxrow), pos, row_offset, 'below') return # check if cursor will stop scroll from taking effect if cursor is not None: x,y = cursor if y+focus_row_offset+1 >= maxrow: # cursor position is a problem, # choose another focus if widget is None: # try harder to get prev widget widget, pos = self.body.get_prev(pos) if widget is None: return # can't do anything rows = widget.rows((maxcol,),focus=1) row_offset -= rows if -row_offset >= rows: # must scroll further than 1 line row_offset = - (rows-1) self.change_focus((maxcol,maxrow),pos, row_offset, 'below') return # if all else fails, just shift the current focus. self.shift_focus((maxcol,maxrow), focus_row_offset+1) def _keypress_down( self, (maxcol, maxrow) ): middle, top, bottom = self.calculate_visible( (maxcol,maxrow), focus=1 ) if middle is None: return 'down' focus_row_offset,focus_widget,focus_pos,focus_rows,cursor=middle trim_bottom, fill_below = bottom row_offset = focus_row_offset + focus_rows rows = focus_rows # look for selectable widget below pos = focus_pos widget = None for widget, pos, rows in fill_below: if widget.selectable(): # this one will do self.change_focus((maxcol,maxrow), pos, row_offset, 'above') return row_offset += rows # at this point we must scroll row_offset -= 1 self._invalidate() if row_offset < maxrow: # need to scroll in another candidate widget widget, pos = self.body.get_next(pos) if widget is None: # cannot scroll any further return 'down' # keypress not handled if widget.selectable(): # this one will do self.change_focus((maxcol,maxrow), pos, row_offset, 'above') return rows = widget.rows((maxcol,)) row_offset += rows if not focus_widget.selectable() or focus_row_offset+focus_rows-1 <= 0: # just take bottom one if current is not selectable # or if focus has moved out of view if widget is None: self.shift_focus((maxcol,maxrow), row_offset-rows) return # FIXME: catch this bug in testcase #self.change_focus((maxcol,maxrow), pos, # row_offset+rows, 'above') self.change_focus((maxcol,maxrow), pos, row_offset-rows, 'above') return # check if cursor will stop scroll from taking effect if cursor is not None: x,y = cursor if y+focus_row_offset-1 < 0: # cursor position is a problem, # choose another focus if widget is None: # try harder to get next widget widget, pos = self.body.get_next(pos) if widget is None: return # can't do anything else: row_offset -= rows
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -