📄 calc.py
字号:
#!/usr/bin/python## Urwid advanced example column calculator application# 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/"""Urwid advanced example column calculator applicationFeatures:- multiple separate list boxes within columns- custom edit widget for editing calculator cells- custom parent widget for links to other columns- custom list walker to show and hide cell results as required- custom wrap and align modes for editing right-1 aligned numbers- outputs commands that may be used to recreate expression on exit"""import urwidimport urwid.raw_displayimport urwid.web_display# use appropriate Screen classif urwid.web_display.is_web_request(): Screen = urwid.web_display.Screenelse: Screen = urwid.raw_display.Screendef div_or_none(a,b): """Divide a by b. Return result or None on divide by zero.""" if b == 0: return None return a/b# operators supported and the functions used to calculate a resultOPERATORS = { '+': (lambda a, b: a+b), '-': (lambda a, b: a-b), '*': (lambda a, b: a*b), '/': div_or_none, }# the uppercase versions of keys used to switch columnsCOLUMN_KEYS = list( "?ABCDEF" )# these lists are used to determine when to display errorsEDIT_KEYS = OPERATORS.keys() + COLUMN_KEYS + ['backspace','delete']MOVEMENT_KEYS = ['up','down','left','right','page up','page down']# Event textE_no_such_column = "Column %s does not exist."E_no_more_columns = "Maxumum number of columns reached."E_new_col_cell_not_empty = "Column must be started from an empty cell."E_invalid_key = "Invalid key '%s'."E_no_parent_column = "There is no parent column to return to."E_cant_combine = "Cannot combine cells with sub-expressions."E_invalid_in_parent_cell = "Cannot enter numbers into parent cell."E_invalid_in_help_col = [ "Help Column is in focus. Press ", ('key',COLUMN_KEYS[1]),"-",('key',COLUMN_KEYS[-1]), " to select another column."]# Shared layout objectCALC_LAYOUT = Noneclass CalcEvent: """Events triggered by user input.""" attr = 'event' def __init__(self, message): self.message = message def widget(self): """Return a widget containing event information""" text = urwid.Text( self.message, 'center' ) return urwid.AttrWrap( text, self.attr )class ColumnDeleteEvent(CalcEvent): """Sent when user wants to delete a column""" attr = 'confirm' def __init__(self, letter, from_parent=0): self.message = ["Press ", ('key',"BACKSPACE"), " again to confirm column removal."] self.letter = letterclass UpdateParentEvent: """Sent when parent columns may need to be updated.""" passclass Cell: def __init__(self, op ): self.op = op self.is_top = op is None self.child = None self.setup_edit() self.result = urwid.Text("", layout=CALC_LAYOUT) def show_result(self, next_cell): """Return whether this widget should display its result. next_cell -- the cell following self or None""" if self.is_top: return False if next_cell is None: return True if self.op == "+" and next_cell.op == "+": return False return True def setup_edit(self): """Create the standard edit widget for this cell.""" self.edit = urwid.IntEdit() if not self.is_top: self.edit.set_caption( self.op + " " ) self.edit.set_layout( None, None, CALC_LAYOUT ) def get_value(self): """Return the numeric value of the cell.""" if self.child is not None: return self.child.get_result() else: return long("0"+self.edit.edit_text) def get_result(self): """Return the numeric result of this cell's operation.""" if self.is_top: return self.get_value() if self.result.text == "": return None return long(self.result.text) def set_result(self, result): """Set the numeric result for this cell.""" if result == None: self.result.set_text("") else: self.result.set_text( "%d" %result ) def become_parent(self, column, letter): """Change the edit widget to a parent cell widget.""" self.child = column self.edit = ParentEdit( self.op, letter ) def remove_child(self): """Change the edit widget back to a standard edit widget.""" self.child = None self.setup_edit() def is_empty( self ): """Return True if the cell is "empty".""" return self.child is None and self.edit.edit_text == ""class ParentEdit(urwid.Edit): """Edit widget modified to link to a child column""" def __init__(self, op, letter): """Use the operator and letter of the child column as caption op -- operator or None letter -- letter of child column remove_fn -- function to call when user wants to remove child function takes no parameters """ urwid.Edit.__init__(self, layout=CALC_LAYOUT) self.op = op self.set_letter( letter ) def set_letter(self, letter): """Set the letter of the child column for display.""" self.letter = letter caption = "("+letter+")" if self.op is not None: caption = self.op+" "+caption self.set_caption(caption) def keypress(self, size, key): """Disable usual editing, allow only removing of child""" if key == "backspace": raise ColumnDeleteEvent(self.letter, from_parent=True) elif key in list("0123456789"): raise CalcEvent, E_invalid_in_parent_cell else: return key class CellWalker(urwid.ListWalker): def __init__(self, content): self.content = urwid.MonitoredList(content) self.content.modified = self._modified self.focus = (0,0) # everyone can share the same divider widget self.div = urwid.Divider("-") def get_cell(self, i): if i < 0 or i >= len(self.content): return None else: return self.content[i] def _get_at_pos(self, pos): i, sub = pos assert sub in (0,1,2) if i < 0 or i >= len(self.content): return None, None if sub == 0: edit = self.content[i].edit return urwid.AttrWrap(edit, 'edit', 'editfocus'), pos elif sub == 1: return self.div, pos else: return self.content[i].result, pos def get_focus(self): return self._get_at_pos(self.focus) def set_focus(self, focus): self.focus = focus def get_next(self, start_from): i, sub = start_from assert sub in (0,1,2) if sub == 0: show_result = self.content[i].show_result( self.get_cell(i+1)) if show_result: return self._get_at_pos( (i, 1) ) else: return self._get_at_pos( (i+1, 0) ) elif sub == 1: return self._get_at_pos( (i, 2) ) else: return self._get_at_pos( (i+1, 0) ) def get_prev(self, start_from): i, sub = start_from assert sub in (0,1,2) if sub == 0: if i == 0: return None, None show_result = self.content[i-1].show_result( self.content[i]) if show_result: return self._get_at_pos( (i-1, 2) ) else: return self._get_at_pos( (i-1, 0) ) elif sub == 1: return self._get_at_pos( (i, 0) ) else: return self._get_at_pos( (i, 1) ) class CellColumn( urwid.WidgetWrap ): def __init__(self, letter): self.walker = CellWalker([Cell(None)]) self.content = self.walker.content self.listbox = urwid.ListBox( self.walker ) self.set_letter( letter ) urwid.WidgetWrap.__init__(self, self.frame) def set_letter(self, letter): """Set the column header with letter.""" self.letter = letter header = urwid.AttrWrap( urwid.Text( ["Column ",('key',letter)], layout = CALC_LAYOUT), 'colhead' ) self.frame = urwid.Frame( self.listbox, header ) def keypress(self, size, key): key = self.frame.keypress( size, key) if key is None: changed = self.update_results() if changed: raise UpdateParentEvent() return f, (i, sub) = self.walker.get_focus() if sub != 0: # f is not an edit widget return key if OPERATORS.has_key(key): # move trailing text to new cell below edit = self.walker.get_cell(i).edit cursor_pos = edit.edit_pos tail = edit.edit_text[cursor_pos:] edit.set_edit_text( edit.edit_text[:cursor_pos] ) new_cell = Cell( key ) new_cell.edit.set_edit_text( tail ) self.content[i+1:i+1] = [new_cell] changed = self.update_results() self.move_focus_next( size ) self.content[i+1].edit.set_edit_pos(0) if changed: raise UpdateParentEvent() return elif key == 'backspace': # unhandled backspace, we're at beginning of number # append current number to cell above, removing operator above = self.walker.get_cell(i-1) if above is None: # we're the first cell raise ColumnDeleteEvent( self.letter, from_parent=False ) edit = self.walker.get_cell(i).edit # check that we can combine if above.child is not None: # cell above is parent if edit.edit_text: # ..and current not empty, no good raise CalcEvent, E_cant_combine above_pos = 0 else: # above is normal number cell above_pos = len(above.edit.edit_text) above.edit.set_edit_text( above.edit.edit_text + edit.edit_text ) self.move_focus_prev( size ) self.content[i-1].edit.set_edit_pos(above_pos) del self.content[i] changed = self.update_results() if changed: raise UpdateParentEvent() return elif key == 'delete': # pull text from next cell into current cell = self.walker.get_cell(i) below = self.walker.get_cell(i+1) if cell.child is not None: # this cell is a parent raise CalcEvent, E_cant_combine if below is None: # nothing below return key if below.child is not None: # cell below is a parent raise CalcEvent, E_cant_combine edit = self.walker.get_cell(i).edit edit.set_edit_text( edit.edit_text + below.edit.edit_text ) del self.content[i+1] changed = self.update_results() if changed: raise UpdateParentEvent() return return key def move_focus_next(self, size): f, (i, sub) = self.walker.get_focus() assert i<len(self.content)-1 ni = i while ni == i: self.frame.keypress(size, 'down') nf, (ni, nsub) = self.walker.get_focus() def move_focus_prev(self, size): f, (i, sub) = self.walker.get_focus() assert i>0 ni = i while ni == i: self.frame.keypress(size, 'up') nf, (ni, nsub) = self.walker.get_focus() def update_results( self, start_from=None ): """Update column. Return True if final result changed. start_from -- Cell to start updating from or None to start from the current focus (default None) """ if start_from is None: f, (i, sub) = self.walker.get_focus() else: i = self.content.index(start_from) if i == None: return False focus_cell = self.walker.get_cell(i) if focus_cell.is_top: x = focus_cell.get_value()
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -