File: units.pyw
   1 #!/usr/bin/python3
   2 
   3 # The MIT License (MIT)
   4 #
   5 # Copyright © 2024 pacman64
   6 #
   7 # Permission is hereby granted, free of charge, to any person obtaining a copy
   8 # of this software and associated documentation files (the “Software”), to deal
   9 # in the Software without restriction, including without limitation the rights
  10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11 # copies of the Software, and to permit persons to whom the Software is
  12 # furnished to do so, subject to the following conditions:
  13 #
  14 # The above copyright notice and this permission notice shall be included in
  15 # all copies or substantial portions of the Software.
  16 #
  17 # THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23 # SOFTWARE.
  24 
  25 
  26 # units.pyw
  27 #
  28 # This is a GUI which lets you run python expression to calculate numbers,
  29 # which are then used to convert from/to many common measurement units.
  30 #
  31 # Updates happen automatically, each time the main input changes.
  32 
  33 
  34 import math
  35 from math import \
  36     acos, acosh, asin, asinh, atan, atan2, atanh, ceil, comb, \
  37     copysign, cos, cosh, degrees, dist, e, erf, erfc, exp, expm1, \
  38     fabs, factorial, floor, fmod, frexp, fsum, gamma, gcd, hypot, inf, \
  39     isclose, isfinite, isinf, isnan, isqrt, lcm, ldexp, lgamma, log, \
  40     log10, log1p, log2, modf, nan, nextafter, perm, pi, pow, prod, \
  41     radians, remainder, sin, sinh, sqrt, tan, tanh, tau, trunc, ulp
  42 try:
  43     from math import cbrt, exp2
  44 except:
  45     pass
  46 
  47 from random import \
  48     betavariate, choice, choices, expovariate, gammavariate, gauss, \
  49     getrandbits, getstate, lognormvariate, normalvariate, paretovariate, \
  50     randbytes, randint, random, randrange, sample, seed, setstate, \
  51     shuffle, triangular, uniform, vonmisesvariate, weibullvariate
  52 
  53 from statistics import \
  54     bisect_left, bisect_right, fmean, \
  55     geometric_mean, harmonic_mean, mean, median, \
  56     median_grouped, median_high, median_low, mode, multimode, pstdev, \
  57     pvariance, quantiles, stdev, variance
  58 try:
  59     from statistics import \
  60         correlation, covariance, linear_regression, mul, reduce
  61 except:
  62     pass
  63 
  64 from tkinter import Tk, Entry, Label, TOP, BOTTOM, LEFT, Event
  65 from typing import List, Tuple, Union, Any
  66 
  67 
  68 # some occasionally-useful values
  69 kb = 1024
  70 mb = 1024 * kb
  71 gb = 1024 * mb
  72 tb = 1024 * gb
  73 pb = 1024 * tb
  74 mole = 602214076000000000000000
  75 mol = mole
  76 
  77 
  78 def check_shortcuts(event: Any) -> None:
  79     char: str = event.char
  80     if char == '':
  81         return
  82     # copy formula and result to clipboard when enter is pressed
  83     if ord(char) == 13:
  84         copy(output['text'])
  85     # quit when esc key is pressed
  86     if ord(char) == 27:
  87         win.quit()
  88 
  89 
  90 def update_result(event: Event) -> None:
  91     try:
  92         expr = input.get()
  93         if expr == '':
  94             return
  95         # square brackets are easier to type and are valid math
  96         expr = expr.replace('[', '(').replace(']', ')')
  97         output['text'] = convert(eval(expr))
  98     except Exception as e:
  99         output['text'] = e
 100 
 101 
 102 def copy(s: str) -> None:
 103     # tkinter's clipboard functionality isn't working
 104     from subprocess import run
 105     for p in ['xclip -i', 'wclip -i', 'clip.exe', 'pbcopy']:
 106         try:
 107             run(p, input=s, text=True)
 108             # copy successful, so return
 109             return
 110         except Exception as _:
 111             pass
 112 
 113 
 114 def convert(x: float) -> str:
 115     t = []
 116     for e in table:
 117         if e is None:
 118             t.append('')
 119         else:
 120             y = e[1] * x + e[2]
 121             t.append('{:,} {:<3} = {:,.4f} {}'.format(x, e[0], y, e[3]))
 122     return '\n'.join(t)
 123 
 124 
 125 # unit conversion table
 126 table: List[Union[Tuple[str, float, float, str], None]] = [
 127     # from, multiplier, offset, to
 128     # none values mark where to put empty lines in the output
 129     ('ft', 0.3048, 0, 'm'),
 130     ('m', 3.28084, 0, 'ft'),
 131     ('in', 2.54, 0, 'cm'),
 132     ('cm', 0.393701, 0, 'in'),
 133     ('mi', 1.60934, 0, 'km'),
 134     ('km', 0.621371, 0, 'mi'),
 135     ('yd', 0.9144, 0, 'm'),
 136     ('m', 1.09361, 0, 'yd'),
 137     None,
 138     ('f', 5/9, -32*5/9, 'c'),
 139     ('c', 9/5, 32, 'f'),
 140     None,
 141     ('lb', 0.453592, 0, 'kg'),
 142     ('kg', 2.20462, 0, 'lb'),
 143     ('oz', 28.3495, 0, 'g'),
 144     ('g', 0.035274, 0, 'oz'),
 145     None,
 146     ('gal', 3.78541, 0, 'l'),
 147     ('l', 0.264172, 0, 'gal'),
 148     None,
 149     ('in2', 6.4516, 0, 'cm2'),
 150     ('cm2', 0.15500031, 0, 'in2'),
 151     ('ft2', 0.092903, 0, 'm2'),
 152     ('m2', 10.7639, 0, 'ft2'),
 153     ('mi2', 2.58999, 0, 'km2'),
 154     ('km2', 0.386102, 0, 'mi2'),
 155     ('ac', 0.00404686, 0, 'km2'),
 156     ('km2', 247.105, 0, 'ac'),
 157 ]
 158 
 159 win = Tk()
 160 win.title('Unit-conversion Calculator')
 161 input = Entry(font=20, width=35)
 162 input.pack(side=TOP, padx=5)
 163 input.bind('<KeyPress>', check_shortcuts)
 164 input.bind('<KeyRelease>', update_result)
 165 input.focus()
 166 
 167 font = ('Lucida Console', 12)
 168 output = Label(text='esc\tquits\nenter\tcopies to clipboard', font=font)
 169 output.config(justify=LEFT)
 170 output.pack(side=BOTTOM, padx=10, pady=5)
 171 win.mainloop()