File: quac.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 # quac.pyw
  27 #
  28 # Quick Adder/Calculator (QuAC) does what it says with a GUI.
  29 
  30 
  31 import math
  32 from math import \
  33     acos, acosh, asin, asinh, atan, atan2, atanh, ceil, comb, \
  34     copysign, cos, cosh, degrees, dist, e, erf, erfc, exp, expm1, \
  35     fabs, factorial, floor, fmod, frexp, fsum, gamma, gcd, hypot, inf, \
  36     isclose, isfinite, isinf, isnan, isqrt, lcm, ldexp, lgamma, log, \
  37     log10, log1p, log2, modf, nan, nextafter, perm, pi, pow, prod, \
  38     radians, remainder, sin, sinh, sqrt, tan, tanh, tau, trunc, ulp
  39 try:
  40     from math import cbrt, exp2
  41 except:
  42     pass
  43 
  44 from random import \
  45     betavariate, choice, choices, expovariate, gammavariate, gauss, \
  46     getrandbits, getstate, lognormvariate, normalvariate, paretovariate, \
  47     randbytes, randint, random, randrange, sample, seed, setstate, \
  48     shuffle, triangular, uniform, vonmisesvariate, weibullvariate
  49 
  50 from statistics import \
  51     bisect_left, bisect_right, fmean, \
  52     geometric_mean, harmonic_mean, mean, median, \
  53     median_grouped, median_high, median_low, mode, multimode, pstdev, \
  54     pvariance, quantiles, stdev, variance
  55 try:
  56     from statistics import \
  57         correlation, covariance, linear_regression, mul, reduce
  58 except:
  59     pass
  60 
  61 from tkinter import Tk, Button, Entry, Frame, Label, END, EventType
  62 from tkinter.messagebox import showerror
  63 from tkinter.ttk import Treeview
  64 
  65 
  66 # some convenience aliases to various funcs from the python stdlib
  67 geomean = geometric_mean
  68 harmean = harmonic_mean
  69 sd = stdev
  70 popsd = pstdev
  71 var = variance
  72 popvar = pvariance
  73 randbeta = betavariate
  74 randexp = expovariate
  75 randgamma = gammavariate
  76 randlognorm = lognormvariate
  77 randnorm = normalvariate
  78 randweibull = weibullvariate
  79 
  80 # some occasionally-useful values
  81 kb = 1024
  82 mb = 1024 * kb
  83 gb = 1024 * mb
  84 tb = 1024 * gb
  85 pb = 1024 * tb
  86 
  87 
  88 # results is a list of formula-result pairs
  89 results = []
  90 
  91 
  92 # quick guide for the treeview-specific api
  93 # https://riptutorial.com/tkinter/example/31880/treeview--basic-example
  94 
  95 
  96 def update() -> None:
  97     '''Evaluates the current value/formula, then updates the bottom table'''
  98 
  99     s = inp.get().strip()
 100     if s == '':
 101         # ignore empty(ish) inputs
 102         return
 103 
 104     try:
 105         v = float(eval(s))
 106         inp.config(bg='white', fg='black')
 107         results.append((s, v))
 108     except:
 109         inp.config(bg='darkred', fg='white')
 110     # clear the output
 111     for c in out.get_children():
 112         out.delete(c)
 113 
 114     total = 0
 115     for e in results:
 116         total += float(e[1])
 117     win.title(f'Σ ≈ {total:,.4f} (QuAC)')
 118 
 119     # update output rows
 120     j = 0  # the actual row-insertion index
 121     for i, e in enumerate(results):
 122         # visually-separate groups of 5 rows by adding an empty one
 123         if i > 0 and i % 5 == 0:
 124             out.insert('', j, text='')
 125             j += 1
 126         val = [e[0], f'{e[1]:,.6f}']
 127         out.insert('', j, text=str(j+1), values=val)
 128         j += 1
 129 
 130 
 131 def check_shortcuts(event: EventType) -> None:
 132     char = event.char
 133     if char == '':
 134         return
 135     # copy formula and result to clipboard when enter is pressed
 136     if ord(char) == 13:
 137         update()
 138     # quit when esc key is pressed
 139     if ord(char) == 27:
 140         win.quit()
 141 
 142 
 143 def clear() -> None:
 144     global results
 145     results = []
 146     # clear the output
 147     for c in out.get_children():
 148         out.delete(c)
 149     # auto-focus on formula input
 150     inp.select_range(0, END)
 151     inp.focus_set()
 152     # reset title too
 153     win.title('Quick Adder/Calculator (QuAC)')
 154 
 155 
 156 try:
 157     win = Tk()
 158     win.title('Quick Adder/Calculator (QuAC)')
 159     win.bind('<Escape>', lambda _: win.quit())
 160 
 161     # top of window: formula-input and button to clear results
 162     top = Frame(win)
 163     top.pack()
 164     Label(top, text='Number or Formula').grid(row=0, column=0, padx=12)
 165     inp = Entry(top, text='', width=28, borderwidth=1)
 166     inp.grid(row=0, column=1, padx=0)
 167     inp.bind('<KeyPress>', check_shortcuts)
 168     inp.select_range(0, END)
 169     inp.focus_set()
 170     cl = Button(top, text='Clear', font=8, command=clear)
 171     cl.grid(row=0, column=2, padx=0, sticky='e')
 172     cl.bind('<KeyPress>', lambda _: clear())
 173 
 174     # bottom of window only has the formula-results table
 175     bottom = Frame(win)
 176     bottom.pack(padx=0)
 177     out = Treeview(bottom, column=['formula', 'result'], height=36)
 178     out.heading('#0', text='#')
 179     out.column('#0',  width=40)
 180     out.heading('formula', text='formula')
 181     out.column('formula', width=200, anchor='e')
 182     out.heading('result', text='result')
 183     out.column('result', width=240, anchor='e')
 184     out.pack()
 185 
 186     # run gui
 187     win.mainloop()
 188 except Exception as e:
 189     showerror('Error', str(e))
 190     win.quit()