screenlets.plugins.Convert

Package screenlets :: Package plugins :: Module Convert |
[hide private]
[frames] | no frames]

# Source Code for Module screenlets.plugins.Convert

      1  # This program is free software: you can redistribute it and/or modify
      2  # it under the terms of the GNU General Public License as published by
      3  # the Free Software Foundation, either version 3 of the License, or
      4  # (at your option) any later version.
      5  #
      6  # This program is distributed in the hope that it will be useful,
      7  # but WITHOUT ANY WARRANTY; without even the implied warranty of
      8  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      9  # GNU General Public License for more details.
     10  #
     11  # You should have received a copy of the GNU General Public License
     12  # along with this program.  If not, see <http://www.gnu.org/licenses/>.
     13
     14  # The base Converter class. Not to be used directly.
     15  # For a commented example on how to write general converter modules, see the
     16  # file BaseConverter.py. But first, please, read below if a RatioConverter isn't
     17  # well suitable for your desired task.
     18
     19 -class Converter(object):
     20          """The base class for the converters. The converters look after maintaining
     21          the list of currently shown values - initialising, accepting keyboard input,
     22          etc.."""
     23
     24          # the name of the class
     25          __name__ = 'Converter'
     26          # a short description to be used for selecting the converter
     27          __title__ = 'Base'
     28
     29          # the number of fields shown on the display between 2 and 4
     30          num_fields = 0
     31          # field captions up to 4 chars long
     32          field_names = []
     33          # zero-based index of field having input focus
     34          active_field = 0
     35          # the list of currently shown 'values'. They should always be strings.
     36          values = []
     37          # replace: True = replace all the input by next keypress
     38          replace = True
     39
     40          # initialize the list of values and fill it with defaults (strings!).
     41          # Override this if you don't want then be '0's.
     42 -        def __init__(self):
     43                  self.values = []
     44                  for i in range(self.num_fields):
     45                          self.values.append('0')
     46                  self.active_field = 0
     47
     48 -        def on_key_press(self, key):
     49                  """If accepted, appends the pressed key to the value in the currently
     50                  active field."""
     51                  translate_dict = {'comma': '.', 'period': '.', 'Add': '+', 'plus': '+',
     52                                  'Subtract': '-', 'minus': '-'}
     53                  if key[:3] == 'KP_':
     54                          key = key[3:]           # keys from numerical keypad come as digits
     55                  if translate_dict.has_key(key):
     56                          key = translate_dict[key]
     57                  if key == 'BackSpace':  # try to guess :-)
     58                          self.values[self.active_field] = self.values[self.active_field][:-1]
     59                  elif key == 'Escape':   # clean the field
     60                          self.values[self.active_field] = ''
     61                  elif key == 'Down':             # move field focus
     62                          if self.active_field < self.num_fields - 1:
     63                                  self.active_field += 1
     64                                  self.replace = True     # after a change, don't append next pressed
     65                                  return True                     # key but start over
     66                          return False
     67                  elif key == 'Up':
     68                          if self.active_field > 0:
     69                                  self.active_field -= 1
     70                                  self.replace = True
     71                                  return True
     72                          return False
     73                  elif key == 'Tab':
     74                          self.active_field += 1
     75                          self.active_field %= self.num_fields
     76                          self.replace = True
     77                          return True
     78                  else:           # leave other keys unchanged
     79                          if not self.filter_key(key):
     80                                  return False
     81                          if self.replace:
     82                                  self.values[self.active_field] = key
     83                          # limit input length to 10 characters
     84                          elif len(self.values[self.active_field]) < 10:
     85                                  self.values[self.active_field] = \
     86                                          self.values[self.active_field] + key
     87                          self.replace = False
     88                  self.neaten_input()
     89                  self.convert()
     90                  return True
     91
     92          # You can use this function instead of str or fixed-width % - it leaves
     93          # 3 digits after the decimal point for floats, and no decimal part if the
     94          # number is integer.
     95          # Serves good if the same converter can be used for both ints ant floats,
     96          # so that the user doesn't get '1.000' when not necessary, also for showing
     97          # '0' instead of '0.000' in all cases.
     98 -        def str(self, val):
     99                  if val == int(val):
    100                          return str(int(val))
    101                  else:
    102                          return '%.3f' % val
    103
    104          # Overridable functions:
    105
    106          # neaten_input: see the docstring
    107          # Return value: none
    108          # If you don't want such function, override it in your converter with pass.
    109          # If you override it by something more complicated, be careful not to modify
    110          # the current value so that it breaks the input.
    111 -        def neaten_input(self):
    112                  """Replaces an empty value with '0' and again removes leading '0' in
    113                  a non-empty string."""
    114                  # This version is aware of the possibility of negative input values,
    115                  # but you must allow + and - keys in filter_key and avoid an error when
    116                  # the input is '-'. See TemperatureConverter for an example.
    117                  if self.values[self.active_field] == '' or \
    118                                  self.values[self.active_field][-1] == '+':
    119                                          self.values[self.active_field] = '0'
    120                  elif self.values[self.active_field][-1] == '-':
    121                          self.values[self.active_field] = '-'
    122                  if len(self.values[self.active_field]) > 1 and \
    123                                  self.values[self.active_field][0] == '0':
    124                          self.values[self.active_field] = self.values[self.active_field][1:]
    125
    126          # filter_key: decides if pressed key can be appended to current value.
    127          # "key" is the GDK name of the key pressed by user
    128          # Return value: True - accept key, False - reject it.
    129          # There is no way to modify the value of key.
    130          # Be prepared for various "keys", e.g., 'apostrophe' must not match 'a'.
    131 -        def filter_key(self, key):
    132                  """Decides whether to accept pressed key."""
    133                  return False
    134
    135          # convert: actualizes field values to reflect input
    136          # Return value: none
    137          # Active value can be modified, but not so that it breaks the input.
    138 -        def convert(self):
    139                  """Recalculates field values after changes."""
    140                  pass
    141
    142
    143  # A subclass of Converter representing numerical converters whose field are
    144  # bound by a simple linear relation. It cares about float value input and output
    145  # and conversion, so the developer who wants to use it doesn't need to provide
    146  # any functions. Besides the __anything__, num_fields and field_names variables,
    147  # he must only provide a list describing desired ratios (see below).
    148  # Not to be used directly. See LengthConverter for a commented example.
    149
    150 -class RatioConverter(Converter):
    151          """A base class of converters having linear dependances between their
    152          fields."""
    153
    154          __name__ = 'RatioConverter'
    155          __title__ = 'Base / Ratio'
    156
    157          # The list of relative weights of the individual fields. The length of this
    158          # list must be equal to num_fields. The ratio between two fields will be
    159          # inverse to the ratio of corresponding entries in this list. For example,
    160          # see LengthConverter. One may ask whether not to better use relative
    161          # ratios, i.e., ratios between fields = ratio between entries, the answer is
    162          # that this approach is more intuitive. You would set 1 for some unit and
    163          # 1000 for the corresponding kilo-unit this way.
    164          # The above implies that the values in this list are relative to whichever
    165          # one you would choose, e.g., [10, 5, 1] (will be converted to float) is
    166          # equivalent to [2.0, 1.0, 0.2]. Actually, integer (exact) values are
    167          # preferred if they reflect the logic of the conversion.
    168          weights = []
    169
    170 -        def filter_key(self, key):
    171                  # Accept digits
    172                  if key.isdigit():
    173                          return True
    174                  # Also accept a decimal point if there is no one yet
    175                  elif key == '.':
    176                          return not ('.' in self.values[self.active_field])
    177                  else:
    178                          return False
    179
    180 -        def convert(self):
    181                  # A multiply-then-divide approach is used to help avoiding little
    182                  # inaccuracies arising in operations like (3/10)*100.
    183                  val = float(self.values[self.active_field]) \
    184                                  * self.weights[self.active_field]
    185                  for i in range(self.num_fields):
    186                          if i == self.active_field:
    187                                  continue
    188                          self.values[i] = self.str(val / float(self.weights[i]))
    189          # Note: if you don't like the Converter.str() function used above which
    190          # tries to always display integers as integers, you can provide your own,
    191          # e.g., def str(self, val): return '%.3f' % val
    192