"""
The main wx.Frame
"""
+ program_name = ""
def __init__(self, *args, **kw):
+ """add to the init process for the wx.Frame"""
super(MainFrame, self).__init__(*args, **kw)
+ # initial setup
self.SetTitle(progname)
self.SetSize(600, 400)
self.props = self.init_props()
-
self.pnl = wx.Panel(self)
- st = wx.StaticText(self.pnl, label=progname)
- font = st.GetFont()
+ # wx item vars
+ self.wx_items = dict()
+ self.wx_items['StaticText'] = dict()
+ self.wx_items['TextEntry'] = dict()
+ self.wx_items['RadioButton'] = dict()
+ self.wx_items['ComboBox'] = dict()
+
+ # setup the GUI itself
+ self.create_wx_items()
+ self.establish_wx_bindings()
+ self.do_wx_layout()
+ self.update_mtc()
+
+ # init routines
+ def init_props(self):
+ """centralized place to initialize props var"""
+ props = dict()
+ props['distance'] = 0
+ props['elevation'] = 0
+ props['fitness'] = 'average'
+ props['units'] = 'imperial'
+ props['travel_mode'] = 'uphill'
+ return props
+
+ def create_wx_items(self):
+ """create wxPython items"""
+ # title bar / program name
+ self.program_name = wx.StaticText(self.pnl, label=progname)
+ font = self.program_name.GetFont()
font.PointSize += 10
font = font.Bold()
- st.SetFont(font)
-
- sizer = wx.BoxSizer(wx.HORIZONTAL)
- sizer.Add(st, wx.SizerFlags().Border(wx.TOP|wx.LEFT, 0))
+ self.program_name.SetFont(font)
# text entry fields
- self.st_distance = wx.StaticText(self.pnl, label="Distance: ", style=wx.ALIGN_RIGHT)
- self.te_distance = wx.TextCtrl(self.pnl, value="0", size=(140, -1))
-
- self.st_elevation = wx.StaticText(self.pnl, label="Elevation: ", style=wx.ALIGN_RIGHT)
- self.te_elevation = wx.TextCtrl(self.pnl, value="0", size=(140, -1))
-
- self.st_fitness = wx.StaticText(self.pnl, label="Fitness: ", style=wx.ALIGN_RIGHT)
+ self.wx_items['StaticText']['distance'] = wx.StaticText(self.pnl,
+ label="Distance: ",
+ style=wx.ALIGN_RIGHT)
+ self.wx_items['TextEntry']['distance'] = wx.TextCtrl(self.pnl,
+ value="0",
+ size=(140, -1))
+
+ self.wx_items['StaticText']['elevation'] = wx.StaticText(self.pnl,
+ label="Elevation: ",
+ style=wx.ALIGN_RIGHT)
+ self.wx_items['TextEntry']['elevation'] = wx.TextCtrl(self.pnl,
+ value="0",
+ size=(140, -1))
+
+ self.wx_items['StaticText']['fitness'] = wx.StaticText(self.pnl,
+ label="Fitness: ",
+ style=wx.ALIGN_RIGHT)
rb_fitness_choices = ['slow', 'average', 'fast']
rb_fitness_default = 'average'
- self.rb_fitness = wx.ComboBox(self.pnl, choices=rb_fitness_choices,
- value=rb_fitness_default, style=wx.CB_READONLY)
-
- self.st_travel_mode = wx.StaticText(self.pnl, label="Travel Mode: ", style=wx.ALIGN_RIGHT)
+ self.wx_items['RadioButton']['fitness'] = wx.ComboBox(self.pnl,
+ choices=rb_fitness_choices,
+ value=rb_fitness_default,
+ style=wx.CB_READONLY)
+
+ self.wx_items['StaticText']['travel_mode'] = wx.StaticText(self.pnl,
+ label="Travel Mode: ",
+ style=wx.ALIGN_RIGHT)
rb_travel_mode_choices = ['uphill', 'flat', 'downhill', 'bushwhacking']
rb_travel_mode_default = 'uphill'
- self.rb_travel_mode = wx.ComboBox(self.pnl,
- choices=rb_travel_mode_choices,
- value=rb_travel_mode_default, style=wx.CB_READONLY)
-
- self.st_units = wx.StaticText(self.pnl, label="Units: ", style=wx.ALIGN_RIGHT)
+ self.wx_items['RadioButton']['travel_mode'] = wx.ComboBox(self.pnl,
+ choices=rb_travel_mode_choices,
+ value=rb_travel_mode_default,
+ style=wx.CB_READONLY)
+
+ self.wx_items['StaticText']['units'] = wx.StaticText(self.pnl,
+ label="Units: ",
+ style=wx.ALIGN_RIGHT)
rb_units_choices = ['imperial', 'metric']
rb_units_default = 'imperial'
- self.rb_units = []
- for choice in range(len(rb_units_choices)):
- label = rb_units_choices[choice]
- style = wx.RB_GROUP if not choice else 0
- self.rb_units.append(wx.RadioButton(self.pnl, label=label, style=style))
+ self.wx_items['RadioButton']['units'] = []
+ for choice in rb_units_choices:
+ label = choice
+ style = wx.RB_GROUP if choice == rb_units_default else 0
+ self.wx_items['RadioButton']['units'].append(wx.RadioButton(self.pnl,
+ label=label,
+ style=style))
# static text
- self.st_mtc = wx.StaticText(self.pnl, label="",
- style=wx.ALIGN_CENTRE_HORIZONTAL)
+ self.wx_items['StaticText']['mtc'] = wx.StaticText(self.pnl,
+ label="",
+ style=wx.ALIGN_CENTRE_HORIZONTAL)
- st_mtc_font = st.GetFont()
+ st_mtc_font = self.program_name.GetFont()
st_mtc_font.PointSize += 10
- self.st_mtc.SetFont(st_mtc_font)
+ self.wx_items['StaticText']['mtc'].SetFont(st_mtc_font)
# buttons
self.b_reset = wx.Button(self.pnl, wx.NewId(), '&Reset', (-1, -1),
- wx.DefaultSize)
-
- # bindings
- self.pnl.Bind(wx.EVT_TEXT, self.update_distance, self.te_distance)
- self.pnl.Bind(wx.EVT_TEXT, self.update_elevation, self.te_elevation)
- self.rb_fitness.Bind(wx.EVT_COMBOBOX, self.update_fitness)
- self.rb_travel_mode.Bind(wx.EVT_COMBOBOX, self.update_travel_mode)
+ wx.DefaultSize)
+
+ def establish_wx_bindings(self):
+ """establish wxPython bindings"""
+ self.pnl.Bind(wx.EVT_TEXT, self.update_distance, self.wx_items['TextEntry']['distance'])
+ self.pnl.Bind(wx.EVT_TEXT, self.update_elevation, self.wx_items['TextEntry']['elevation'])
+ self.wx_items['RadioButton']['fitness'].Bind(wx.EVT_COMBOBOX, self.update_fitness)
+ self.wx_items['RadioButton']['travel_mode'].Bind(wx.EVT_COMBOBOX, self.update_travel_mode)
self.b_reset.Bind(wx.EVT_BUTTON, self.reset)
- for cb in self.rb_units:
- cb.Bind(wx.EVT_RADIOBUTTON, self.update_units)
+ for rb_unit in self.wx_items['RadioButton']['units']:
+ rb_unit.Bind(wx.EVT_RADIOBUTTON, self.update_units)
- # layout
- b = 5
- w = 100
+ def do_wx_layout(self):
+ """layout the wxPython items"""
+ border = 5
+ width = 100
+
+ sizer = wx.BoxSizer(wx.HORIZONTAL)
+ sizer.Add(self.program_name, wx.SizerFlags().Border(wx.TOP|wx.LEFT, 0))
static_line = wx.StaticLine(self.pnl, wx.NewId(), style=wx.LI_HORIZONTAL)
hsizer_distance = wx.BoxSizer(wx.HORIZONTAL)
- hsizer_distance.Add(self.st_distance, 0, wx.RIGHT, b)
- hsizer_distance.Add(self.te_distance, 1, wx.GROW, b)
- hsizer_distance.SetItemMinSize(self.st_distance, (w, -1))
+ hsizer_distance.Add(self.wx_items['StaticText']['distance'], 0, wx.RIGHT, border)
+ hsizer_distance.Add(self.wx_items['TextEntry']['distance'], 1, wx.GROW, border)
+ hsizer_distance.SetItemMinSize(self.wx_items['StaticText']['distance'], (width, -1))
hsizer_elevation = wx.BoxSizer(wx.HORIZONTAL)
- hsizer_elevation.Add(self.st_elevation, 0, wx.RIGHT, b)
- hsizer_elevation.Add(self.te_elevation, 1, wx.GROW, b)
- hsizer_elevation.SetItemMinSize(self.st_elevation, (w, -1))
+ hsizer_elevation.Add(self.wx_items['StaticText']['elevation'], 0, wx.RIGHT, border)
+ hsizer_elevation.Add(self.wx_items['TextEntry']['elevation'], 1, wx.GROW, border)
+ hsizer_elevation.SetItemMinSize(self.wx_items['StaticText']['elevation'], (width, -1))
hsizer_fitness = wx.BoxSizer(wx.HORIZONTAL)
- hsizer_fitness.Add(self.st_fitness, 0, wx.RIGHT, b)
- hsizer_fitness.Add(self.rb_fitness, 1, wx.GROW, b)
- hsizer_fitness.SetItemMinSize(self.st_fitness, (w, -1))
+ hsizer_fitness.Add(self.wx_items['StaticText']['fitness'], 0, wx.RIGHT, border)
+ hsizer_fitness.Add(self.wx_items['RadioButton']['fitness'], 1, wx.GROW, border)
+ hsizer_fitness.SetItemMinSize(self.wx_items['StaticText']['fitness'], (width, -1))
hsizer_travel_mode = wx.BoxSizer(wx.HORIZONTAL)
- hsizer_travel_mode.Add(self.st_travel_mode, 0, wx.RIGHT, b)
- hsizer_travel_mode.Add(self.rb_travel_mode, 1, wx.GROW, b)
- hsizer_travel_mode.SetItemMinSize(self.st_travel_mode, (w, -1))
+ hsizer_travel_mode.Add(self.wx_items['StaticText']['travel_mode'], 0, wx.RIGHT, border)
+ hsizer_travel_mode.Add(self.wx_items['RadioButton']['travel_mode'], 1, wx.GROW, border)
+ hsizer_travel_mode.SetItemMinSize(self.wx_items['StaticText']['travel_mode'], (width, -1))
hsizer_units = wx.BoxSizer(wx.HORIZONTAL)
- hsizer_units.Add(self.st_units, 0, wx.RIGHT, b)
- for cb in range(len(self.rb_units)):
- hsizer_units.Add(self.rb_units[cb], cb + 1, wx.GROW, b)
- hsizer_units.SetItemMinSize(self.st_units, (w, -1))
+ hsizer_units.Add(self.wx_items['StaticText']['units'], 0, wx.RIGHT, border)
+ for rb_unit in range(len(self.wx_items['RadioButton']['units'])):
+ hsizer_units.Add(self.wx_items['RadioButton']['units'][rb_unit],
+ rb_unit + 1,
+ wx.GROW,
+ border)
+ hsizer_units.SetItemMinSize(self.wx_items['StaticText']['units'], (width, -1))
hsizer_mtc = wx.BoxSizer(wx.HORIZONTAL)
- hsizer_mtc.Add(self.st_mtc, 1, wx.GROW, b)
- hsizer_mtc.SetItemMinSize(self.st_mtc, (w, -1))
+ hsizer_mtc.Add(self.wx_items['StaticText']['mtc'], 1, wx.GROW, border)
+ hsizer_mtc.SetItemMinSize(self.wx_items['StaticText']['mtc'], (width, -1))
hsizer5 = wx.BoxSizer(wx.HORIZONTAL)
hsizer5.Add(self.b_reset, 0)
- b = 5
vsizer1 = wx.BoxSizer(wx.VERTICAL)
- vsizer1.Add(sizer, 0, wx.EXPAND | wx.ALL, b*b)
- vsizer1.Add(hsizer_distance, 0, wx.EXPAND | wx.ALL, b)
- vsizer1.Add(hsizer_elevation, 0, wx.EXPAND | wx.ALL, b)
- vsizer1.Add(hsizer_fitness, 0, wx.EXPAND | wx.ALL, b)
- vsizer1.Add(hsizer_travel_mode, 0, wx.EXPAND | wx.ALL, b)
- vsizer1.Add(hsizer_units, 0, wx.EXPAND | wx.ALL, b)
- vsizer1.Add(hsizer_mtc, 0, wx.EXPAND | wx.ALL, b)
- vsizer1.Add(static_line, 0, wx.GROW | wx.ALL, b)
- vsizer1.Add(hsizer5, 0, wx.ALIGN_RIGHT | wx.ALL, b)
+ vsizer1.Add(sizer, 0, wx.EXPAND | wx.ALL, border*border)
+ vsizer1.Add(hsizer_distance, 0, wx.EXPAND | wx.ALL, border)
+ vsizer1.Add(hsizer_elevation, 0, wx.EXPAND | wx.ALL, border)
+ vsizer1.Add(hsizer_fitness, 0, wx.EXPAND | wx.ALL, border)
+ vsizer1.Add(hsizer_travel_mode, 0, wx.EXPAND | wx.ALL, border)
+ vsizer1.Add(hsizer_units, 0, wx.EXPAND | wx.ALL, border)
+ vsizer1.Add(hsizer_mtc, 0, wx.EXPAND | wx.ALL, border)
+ vsizer1.Add(static_line, 0, wx.GROW | wx.ALL, border)
+ vsizer1.Add(hsizer5, 0, wx.ALIGN_RIGHT | wx.ALL, border)
self.pnl.SetSizerAndFit(vsizer1)
self.pnl.SetClientSize(vsizer1.GetSize())
- self.update_mtc()
-
- def init_props(self):
- props = dict()
- props['distance'] = 0
- props['elevation'] = 0
- props['fitness'] = 'average'
- props['units'] = 'imperial'
- props['travel_mode'] = 'uphill'
- return props
+ # event handlers
def update_distance(self, event):
- value = self.te_distance.GetValue()
+ """updates distance prop"""
+ value = event.GetEventObject().GetValue()
if value:
try:
new_val = float(value)
self.props['distance'] = new_val
- except:
+ except ValueError:
# reset GUI to last-accepted val
- self.te_distance.SetValue(str(self.props['distance']))
- pass
+ self.wx_items['TextEntry']['distance'].SetValue(str(self.props['distance']))
self.update_mtc()
def update_elevation(self, event):
- value = self.te_elevation.GetValue()
+ """updates elevation prop"""
+ value = event.GetEventObject().GetValue()
if value:
try:
new_val = int(value)
self.props['elevation'] = new_val
- except:
+ except ValueError:
# reset GUI to last-accepted val
- self.te_elevation.SetValue(str(self.props['elevation']))
- pass
+ self.wx_items['TextEntry']['elevation'].SetValue(str(self.props['elevation']))
self.update_mtc()
def update_fitness(self, event):
- value = self.rb_fitness.GetValue()
+ """updates fitness prop"""
+ value = event.GetEventObject().GetValue()
if value:
self.props['fitness'] = value
self.update_mtc()
def update_travel_mode(self, event):
- value = self.rb_travel_mode.GetValue()
+ """updates travel_mode prop"""
+ value = event.GetEventObject().GetValue()
if value:
self.props['travel_mode'] = value
self.update_mtc()
def update_units(self, event):
- rb = event.GetEventObject()
- value = rb.GetLabel()
+ """updates units prop"""
+ value = event.GetEventObject().GetLabel()
if value:
self.props['units'] = value
self.update_mtc()
+ def reset(self, event):
+ """resets all values"""
+ event.GetTimestamp()
+ self.props = self.init_props()
+ self.wx_items['TextEntry']['distance'].SetValue(str(self.props['distance']))
+ self.wx_items['TextEntry']['elevation'].SetValue(str(self.props['elevation']))
+ self.wx_items['RadioButton']['fitness'].SetValue(str(self.props['fitness']))
+ self.wx_items['RadioButton']['travel_mode'].SetValue(str(self.props['travel_mode']))
+ # leave units as the user selected
+ self.update_mtc()
+
+ # other routines
def update_mtc(self):
+ """updates mtc, the final result the user wants"""
if (self.props['distance'] is None) or (self.props['elevation'] is None):
return
est = munter.time_calc(self.props['distance'],
- self.props['elevation'],
- self.props['fitness'],
- self.props['travel_mode'],
- self.props['units'])
+ self.props['elevation'],
+ self.props['fitness'],
+ self.props['travel_mode'],
+ self.props['units'])
hours = int(est['time'])
minutes = int((est['time'] - hours) * 60)
- self.st_mtc.SetLabel("{human_time}".format(
- human_time="{hours} hours {minutes} minutes".format(
- hours=hours,
- minutes=minutes)))
+ self.wx_items['StaticText']['mtc'].SetLabel("{human_time}".format(
+ human_time="{hours} hours {minutes} minutes".format(
+ hours=hours,
+ minutes=minutes)))
self.pnl.Layout()
- def reset(self, event):
- self.props = self.init_props()
- self.te_distance.SetValue(str(self.props['distance']))
- self.te_elevation.SetValue(str(self.props['elevation']))
- self.rb_fitness.SetValue(str(self.props['fitness']))
- self.rb_travel_mode.SetValue(str(self.props['travel_mode']))
- # leave units as the user selected
- self.update_mtc()
-
def startup():
+ """kick off the GUI"""
app = wx.App()
frm = MainFrame(None)
frm.Show()
from . import __version__ as version
class InvalidUnitsException(Exception):
- pass
+ """Exception class for when invalid units are specified"""
-rates = {
- 'uphill': { 'rate': 4, 'direction': '↑' },
- 'flat': { 'rate': 6, 'direction': '→' }, # or downhill on foot
- 'downhill': { 'rate': 10, 'direction': '↓' },
- 'bushwhacking': { 'rate': 2, 'direction': '↹' },
+RATES = {
+ 'uphill': {'rate': 4, 'direction': '↑'},
+ 'flat': {'rate': 6, 'direction': '→'}, # or downhill on foot
+ 'downhill': {'rate': 10, 'direction': '↓'},
+ 'bushwhacking': {'rate': 2, 'direction': '↹'},
}
-fitnesses = {
+FITNESSES = {
'slow': 1.2,
'average': 1,
'fast': .7,
}
-unit_choices = ['metric', 'imperial']
-travel_mode_choices = rates.keys()
-fitness_choices = fitnesses.keys()
+UNIT_CHOICES = ['metric', 'imperial']
+TRAVEL_MODE_CHOICES = RATES.keys()
+FITNESS_CHOICES = FITNESSES.keys()
def time_calc(distance, elevation, fitness='average', rate='uphill',
- units='imperial'):
+ units='imperial'):
+ """
+ the heart of the program, the Munter time calculation implementation
+ """
retval = {}
- if units not in unit_choices:
+ if units not in UNIT_CHOICES:
raise InvalidUnitsException
unit_count = 0
- if 'imperial' == units:
+ if units == 'imperial':
# convert to metric
distance = (distance * 1.609) # mi to km
elevation = (elevation * .305) # ft to m
unit_count = distance + (elevation / 100.0)
- retval['time'] = (distance + (elevation / 100.0)) / rates[rate]['rate']
- retval['time'] = retval['time'] * fitnesses[fitness]
+ retval['time'] = (distance + (elevation / 100.0)) / RATES[rate]['rate']
+ retval['time'] = retval['time'] * FITNESSES[fitness]
retval['unit_count'] = unit_count
- retval['direction'] = rates[rate]['direction']
- retval['pace'] = rates[rate]['rate']
+ retval['direction'] = RATES[rate]['direction']
+ retval['pace'] = RATES[rate]['rate']
return retval
def print_ugly_estimate(est):
+ """plain-jane string containing result"""
hours = int(est['time'])
minutes = int((est['time'] - hours) * 60)
print("{human_time}".format(
- human_time="{hours} hours {minutes} minutes".format(
- hours=hours,
- minutes=minutes)))
+ human_time="{hours} hours {minutes} minutes".format(
+ hours=hours, minutes=minutes)))
def print_pretty_estimate(est):
+ """more elaborate, console-based 'GUI' displaying result"""
hours = int(est['time'])
minutes = int((est['time'] - hours) * 60)
print("\t╎▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒╎╮")
print("\t╎▒{:^29}▒╎│".format(''))
print("\t╎▒{pace_readable:^31}▒╎│".format(
- pace_readable="{units} {direction} @ {pace}".format(
- units=round(est['unit_count']),
- direction=est['direction'],
- pace=est['pace'])))
+ pace_readable="{units} {direction} @ {pace}".format(
+ units=round(est['unit_count']),
+ direction=est['direction'],
+ pace=est['pace'])))
print("\t╎▒{human_time:^29}▒╎│".format(
- human_time="{hours} hours {minutes} minutes".format(
- hours=hours,
- minutes=minutes)))
+ human_time="{hours} hours {minutes} minutes".format(
+ hours=hours,
+ minutes=minutes)))
print("\t╎▒{:^29}▒╎│".format(''))
print("\t╎▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒╎│")
print("\t╘═══════════════════════════════╛│")
print("\t └───────────────────────────────┘\n")
def get_parser():
+ """return ArgumentParser for this program"""
parser = argparse.ArgumentParser(description='Implementation of '
- 'the Munter time calculation')
+ 'the Munter time calculation')
# No required args anymore, since -g overrides any requirement
parser.add_argument('--distance',
- '-d',
- default=0.0,
- type=float,
- required=False,
- help='Distance (in km, by default)')
+ '-d',
+ default=0.0,
+ type=float,
+ required=False,
+ help='Distance (in km, by default)')
parser.add_argument('--elevation',
- '-e',
- default=0.0,
- type=float,
- required=False,
- help='Elevation change (in m, by default)')
+ '-e',
+ default=0.0,
+ type=float,
+ required=False,
+ help='Elevation change (in m, by default)')
parser.add_argument('--travel-mode',
- '-t',
- type=str,
- default='uphill',
- choices=travel_mode_choices, required=False,
- help='Travel mode (uphill, by default)')
+ '-t',
+ type=str,
+ default='uphill',
+ choices=TRAVEL_MODE_CHOICES, required=False,
+ help='Travel mode (uphill, by default)')
parser.add_argument('--fitness',
- '-f',
- type=str,
- default='average',
- choices=fitness_choices, required=False,
- help='Fitness modifier (average, by default)')
+ '-f',
+ type=str,
+ default='average',
+ choices=FITNESS_CHOICES, required=False,
+ help='Fitness modifier (average, by default)')
parser.add_argument('--units',
- '-u',
- type=str,
- default='imperial',
- required=False,
- choices=unit_choices,
- help='Units of input values')
+ '-u',
+ type=str,
+ default='imperial',
+ required=False,
+ choices=UNIT_CHOICES,
+ help='Units of input values')
parser.add_argument('--pretty',
- '-p',
- action='store_true',
- default=False,
- required=False,
- help="Make output pretty");
+ '-p',
+ action='store_true',
+ default=False,
+ required=False,
+ help='Make output pretty')
parser.add_argument('--gui',
- '-g',
- action='store_true',
- default=False,
- required=False,
- help='Launch GUI mode (overrides --pretty)')
+ '-g',
+ action='store_true',
+ default=False,
+ required=False,
+ help='Launch GUI mode (overrides --pretty)')
parser.add_argument('--version',
- '-v',
- action='store_true',
- default=False,
- required=False,
- help='Print version and exit')
+ '-v',
+ action='store_true',
+ default=False,
+ required=False,
+ help='Print version and exit')
return parser
def main():
+ """main routine: sort through args, decide what to do"""
parser = get_parser()
opts = parser.parse_args()
return 0
time_estimate = time_calc(distance=distance, elevation=elevation,
- fitness=fitness, rate=travel_mode, units=units)
+ fitness=fitness, rate=travel_mode,
+ units=units)
if gui:
from . import gui