Simple calculator

Updated TrustyTony 1 Tallied Votes 2K Views Share

Here little debugged version of my calculator posted earlier in GUI calculator thread. I prepared it after listening that their teacher did more limited calculator in around 150 lines of code with their graphics module.

This is 115 lines without empty lines and comment lines

>>> li=[i for i in open('SimplistiCalc.pyw')
... if i.strip() and not i.strip().startswith('#')]
>>> len(li)
115
>>>

Save attached gifs to same directory as the code.

Comments welcome!

## simple calculator inspired by GUI calculator thread, but using Tkinter 

## solve division problems
from __future__ import division
from Tkinter import *
from math import *
from random import random

hyps=arc=''
mem=0
resflag=False ## for clearing the display after result automatically without CE

fact=factorial

def click_feedback():
    pass

def ev(s):
    if '__' in s: return 'Safety Error'
    ## try to fix bracket unbalance 'sin(pi' for example
    brackc = s.count('(') - s.count(')')
    try:    return eval((s + ')' * brackc).replace(',','.')) ## decimal point not comma
    except: return 'Error'
	
def endswithint(s):
    return not s.rstrip('0123456789').endswith('.')

## old solution for division not giving 1/3=0
## enable instead of __future__
## if you are using elder Python version than version 2.6
## also uncomment the line marked **** ('/' key)
##
##def endswithint(s):
##    return not s.rstrip('0123456789').endswith('.')
##
##def addpoint(s):
##    """ returns '.' if s ends with integer, nothing if it endswith float """
##    if endswithint(s): return '.'
##    return ""
##
def calc(s):
    global resflag
    display.set(ev(display.get()))
    number.icursor('end')
    resflag=True
	
def push_key(e):
    global resflag
    if display.get()=='Error': clear(e)
    if 38 <= e.x < 38 + 5 * 32 and 110 <= e.y < 110 + 4 * 31:
        x,y = ((e.x - 38) // 32, (e.y - 110) // 31)
        key=keys[y][x]
        if isinstance(key,basestring):
            if resflag and display.get() and key in '0123456789':
                clear(e)
            resflag=False

## ****
##            if key=='/':
##                key=addpoint(display.get())+key
                
            number.insert('insert',key)
##            number.icursor('end')
        else:
            key(e)
        click_feedback()

    if 34 <= e.x < 34 + 28 * 6 and 28 <= e.y < 24 * 3 + 28:
        x,y = ((e.x - 34) // 28, (e.y - 28) // 24)
        fkey=fkeys[y][x]
        if isinstance(fkey,basestring):
            if fkey in ('sin','cos','tan'):
                display.set(hyp(fkey)+display.get())
            else:
                if (fkey[:2] in ['**','pi','fa']) or(fkey[0] in '()'):
                    number.insert('insert',fkey)
                else:   display.set(fkey+display.get())
            number.icursor('end')
        else: fkey(e)
        resflag=False
 
        click_feedback()

def clear(e): display.set('')

def ac(e):
    global mem
    clear(e)
    mem=0

def mp(e):
    global mem
    calc(e)
    mem=mem+float(display.get())
    
def Min(e):
    global mem
    calc(e)
    mem=float(display.get())
    
def MR(e):
    global mem
    if display.get() and display.get()[-1] in '0123456789':
        display.set(str(mem))
    else:
        display.set(display.get()+str(mem))
    number.icursor('end')
    
def ln(x): return log(x,e)

def hyptoggle(e):
    global hyps
    if hyps:    hyps = ''
    else:       hyps = 'h'
    
def shifttoggle(e):
    global arc,fkeys
    if arc:
        arc =  ''
        panel1.config(image=image1)
        fkeys=bfkeys
    else:
        arc = 'a'
        panel1.config(image=image2)
        fkeys=afkeys       

def hyp(trig): return arc+trig+hyps+'('

def sign(e):
    display.set('-'+display.get())
    calc(e)

def bs(e):
    if display.get(): display.set(display.get()[:-1])

def xtox(e): display.set(str(display.get())+'**'+str(display.get()))

bfkeys=(('1./(','**0.5','**2','log10(','ln(','**'),
       (shifttoggle,'pi',hyptoggle,'sin','cos','tan'),
       (sign, bs, '(',')',Min,MR))

afkeys=(('fact(','sqrt(',xtox,'10**','e**','**(1./'),
       (shifttoggle,'random()',hyptoggle,'sin','cos','tan'), ## sin,cos,tan by function
       (sign, bs, '(',')',Min,MR))

fkeys=bfkeys

keys=(('7','8','9',clear,ac),
      ('4','5','6','*','/'),
      ('1','2','3','+','-'),
      ('0','.','e',calc,mp))

root=Tk()
root.protocol("WM_DELETE_WINDOW",root.destroy)
display = StringVar()
number=Entry(root,textvariable=display, justify='center')
number.pack(side='top', fill='x', expand='no')
image1 = PhotoImage(file="calc.gif")
image2 = PhotoImage(file="calc2.gif")
w,h = image1.width(), image1.height()
root.title('SimplistiCalc')
root.geometry ="%dx%d+0+0" % (w, h)
panel1 = Label(root, image=image1)
panel1.pack(side='top', fill='both', expand='yes')
number.focus()
number.bind('<Return>',calc)
number.bind('<Escape>',clear)
root.bind('<Button-1>',push_key)
root.wait_window()
TrustyTony 888 pyMod Team Colleague Featured Poster

line 29 comment missing .. if you are using elder Python version than version 2.6

Ene Uran 638 Posting Virtuoso

Now it works, nice concept!

TrustyTony 888 pyMod Team Colleague Featured Poster

It is quite easy to adapt to your own style by changing the pictures.

What little irritates me is the simulation of normal calculators 'hidden reverse calculation' like:
2*pi=sin

Maybe better to write version with global _ with last result (the ev passes single underscore) and take out all special conditions to add function to left instead of right.

Writing sin(1)+cos(3) requires now to push Min after sin:
1 sin Min CE 3 cos + MR = (OK, Min does not handle resflag properly OK. Lets move reset inside if)

Memory active indicator or (hyp key indicator) is also missing because of I just did not come around to implement it (I did for that GUI thread with that other guys calculator, one letter box with letter or not letter)

hyp key is only active until next function selection in normal calculator. Here it is until reselected. Just change like this if you want reseting hyp key after trig function press.

here is also change for Min to use resflag (resflag=False moved):

if 34 <= e.x < 34 + 28 * 6 and 28 <= e.y < 24 * 3 + 28:
        x,y = ((e.x - 34) // 28, (e.y - 28) // 24)
        fkey=fkeys[y][x]
        if isinstance(fkey,basestring):
            if fkey in ('sin','cos','tan'):
                display.set(hyp(fkey)+display.get())
                if hyps: hyptoggle(e)
            else:
                if (fkey[:2] in ['**','pi','fa']) or(fkey[0] in '()'):
                    number.insert('insert',fkey)
                else:   display.set(fkey+display.get())
            number.icursor('end')
            resflag=False
        else:
            fkey(e)
 
        click_feedback()

Then there is the click_feedback() to be implemented, optional sounds or visual effect from key push...

TrustyTony 888 pyMod Team Colleague Featured Poster

calc23calc31calc5

Here still another Zip package with functions being inserted at cursor position like typing (simpler) and _ key instead of 1/x (use Home 1/ Enter from keyboard for that) to take last result multiple times in one formula. The result clearing is also removed. Length without comments and empty lines 110 lines.

## simple calculator inspired by GUI calculator thread, but using Tkinter 

## solve division problems
from __future__ import division
from Tkinter import *
from math import *
from random import random

hyps=arc=''
mem=0
_ = 0 ## for keeping last result in memory
fact=factorial

def click_feedback():
    pass

def ev(s):
    if '__' in s: return 'Safety Error'
    ## try to fix bracket unbalance 'sin(pi' for example
    brackc = s.count('(') - s.count(')')
    try:    return eval(s + ')' * brackc)
    except: return 'Error'

## old solution for division not giving 1/3=0
## enable instead of __future__ if you use Python < 2.6
## also uncomment the line marked **** ('/' key)
##
##def endswithint(s):
##    return not s.rstrip('0123456789').endswith('.')
##
##def addpoint(s):
##    """ returns '.' if s ends with integer, nothing if it endswith float """
##    if endswithint(s): return '.'
##    return ""
##
def calc(s):
    global _
    _ = ev(display.get())
    display.set(_)
    number.icursor('end')

def push_key(e):
    if display.get()=='Error' : clear(e)
    if 38 <= e.x < 38 + 5 * 32 and 110 <= e.y < 110 + 4 * 31:
        x,y = ((e.x - 38) // 32, (e.y - 110) // 31)
        key=keys[y][x]
        if isinstance(key,basestring):
## ****
##            if key=='/':
##                key=addpoint(display.get())+key

            number.insert('insert',key)
        else: key(e)
        click_feedback()

    if 34 <= e.x < 34 + 28 * 6 and 28 <= e.y < 24 * 3 + 28:
        x,y = ((e.x - 34) // 28, (e.y - 28) // 24)
        fkey=fkeys[y][x]
        if isinstance(fkey,basestring):
            if fkey in ('sin','cos','tan'):
                fkey=hyp(fkey)
                if hyps: hyptoggle(e)
            number.insert('insert',fkey)
        else:
            fkey(e)

        click_feedback()

def clear(e): display.set('')

def ac(e):
    global mem
    clear(e)
    mem=0

def mp(e):
    global mem
    calc(e)
    mem=mem+float(display.get())

def Min(e):
    global mem
    calc(e)
    mem=float(display.get())

def MR(e):
    global mem
    if display.get() and display.get()[-1] in '0123456789':
        display.set(str(mem))
    else:
        display.set(display.get()+str(mem))
    number.icursor('end')

def ln(x): return log(x,e)

def hyptoggle(e):
    global hyps
    if hyps:    hyps = ''
    else:       hyps = 'h'

def shifttoggle(e):
    global arc,fkeys
    if arc:
        arc =  ''
        panel1.config(image=image1)
        fkeys=bfkeys
    else:
        arc = 'a'
        panel1.config(image=image2)
        fkeys=afkeys       

def hyp(trig): return arc+trig+hyps+'('

def sign(e):
    display.set('-'+display.get())
    calc(e)

def bs(e):
    if display.get(): display.set(display.get()[:-1])

def xtox(e): display.set(str(display.get())+'**'+str(display.get()))

bfkeys=(('_','**0.5','**2','log10(','ln(','**'),
       (shifttoggle,'pi',hyptoggle,'sin','cos','tan'),
       (sign, bs, '(',')',Min,MR))

afkeys=(('fact(','sqrt(',xtox,'10**','e**','**(1./'),
       (shifttoggle,'random()',hyptoggle,'sin','cos','tan'), ## sin,cos,tan by function
       (sign, bs, '(',')',Min,MR))

fkeys=bfkeys

keys=(('7','8','9',clear,ac),
      ('4','5','6','*','/'),
      ('1','2','3','+','-'),
      ('0','.','e',calc,mp))

root=Tk()
root.protocol("WM_DELETE_WINDOW",root.destroy)
display = StringVar()
number=Entry(root,textvariable=display, justify='center')
number.pack(side='top', fill='x', expand='no')
image1 = PhotoImage(file="calc3.gif")
image2 = PhotoImage(file="calc2.gif")
w,h = image1.width(), image1.height()
root.title('SimplistiCalc')
root.geometry ="%dx%d+0+0" % (w, h)
panel1 = Label(root, image=image1)
panel1.pack(side='top', fill='both', expand='yes')
number.focus()
number.bind('<Return>',calc)
number.bind('<Escape>',clear)
root.bind('<Button-1>',push_key)
root.wait_window()

Fixed older version again:

## simple calculator inspired by GUI calculator thread, but using Tkinter 
## original version fixed

## solve division problems
from __future__ import division
from Tkinter import *
from math import *
from random import random

hyps=arc=''
mem=0
resflag=False ## for clearing the display after result automatically without CE

fact=factorial

def click_feedback():
    pass

def ev(s):
    if '__' in s: return 'Safety Error'
    ## try to fix bracket unbalance 'sin(pi' for example
    brackc = s.count('(') - s.count(')')
    try:    return eval(s + ')' * brackc)
    except: return 'Error'

## old solution for division not giving 1/3=0
## enable instead of __future__ if you use Python < 2.6
## also uncomment the line marked **** ('/' key)
##
##def endswithint(s):
##    return not s.rstrip('0123456789').endswith('.')
##
##def addpoint(s):
##    """ returns '.' if s ends with integer, nothing if it endswith float """
##    if endswithint(s): return '.'
##    return ""
##
def calc(s):
    global resflag
    display.set(ev(display.get()))
    number.icursor('end')
    resflag=True

def push_key(e):
    global resflag
    if display.get()=='Error': clear(e)
    if 38 <= e.x < 38 + 5 * 32 and 110 <= e.y < 110 + 4 * 31:
        x,y = ((e.x - 38) // 32, (e.y - 110) // 31)
        key=keys[y][x]
        if isinstance(key,basestring):
            if resflag and display.get() and key in '0123456789':
                clear(e)
            resflag=False

## ****
##            if key=='/':
##                key=addpoint(display.get())+key

            number.insert('insert',key)
##            number.icursor('end')
        else:
            key(e)
        click_feedback()

    if 34 <= e.x < 34 + 28 * 6 and 28 <= e.y < 24 * 3 + 28:
        x,y = ((e.x - 34) // 28, (e.y - 28) // 24)
        fkey=fkeys[y][x]
        if isinstance(fkey,basestring):
            if fkey in ('sin','cos','tan'):
                display.set(hyp(fkey)+display.get())
                if hyps: hyptoggle(e)
            else:
                if (fkey[:2] in ['**','pi','fa']) or(fkey[0] in '()'):
                    number.insert('insert',fkey)
                else:   display.set(fkey+display.get())
            number.icursor('end')
            resflag=False
        else:
            fkey(e)

        click_feedback()

def clear(e): display.set('')

def ac(e):
    global mem
    clear(e)
    mem=0

def mp(e):
    global mem
    calc(e)
    mem=mem+float(display.get())

def Min(e):
    global mem
    calc(e)
    mem=float(display.get())

def MR(e):
    global mem
    if display.get() and display.get()[-1] in '0123456789':
        display.set(str(mem))
    else:
        display.set(display.get()+str(mem))
    number.icursor('end')

def ln(x): return log(x,e)

def hyptoggle(e):
    global hyps
    if hyps:    hyps = ''
    else:       hyps = 'h'

def shifttoggle(e):
    global arc,fkeys
    if arc:
        arc =  ''
        panel1.config(image=image1)
        fkeys=bfkeys
    else:
        arc = 'a'
        panel1.config(image=image2)
        fkeys=afkeys       

def hyp(trig): return arc+trig+hyps+'('

def sign(e):
    display.set('-'+display.get())
    calc(e)

def bs(e):
    if display.get(): display.set(display.get()[:-1])

def xtox(e): display.set(str(display.get())+'**'+str(display.get()))

bfkeys=(('1./(','**0.5','**2','log10(','ln(','**'),
       (shifttoggle,'pi',hyptoggle,'sin','cos','tan'),
       (sign, bs, '(',')',Min,MR))

afkeys=(('fact(','sqrt(',xtox,'10**','e**','**(1./'),
       (shifttoggle,'random()',hyptoggle,'sin','cos','tan'), ## sin,cos,tan by function
       (sign, bs, '(',')',Min,MR))

fkeys=bfkeys

keys=(('7','8','9',clear,ac),
      ('4','5','6','*','/'),
      ('1','2','3','+','-'),
      ('0','.','e',calc,mp))

root=Tk()
root.protocol("WM_DELETE_WINDOW",root.destroy)
display = StringVar()
number=Entry(root,textvariable=display, justify='center')
number.pack(side='top', fill='x', expand='no')
image1 = PhotoImage(file="calc.gif")
image2 = PhotoImage(file="calc2.gif")
w,h = image1.width(), image1.height()
root.title('SimplistiCalc')
root.geometry ="%dx%d+0+0" % (w, h)
panel1 = Label(root, image=image1)
panel1.pack(side='top', fill='both', expand='yes')
number.focus()
number.bind('<Return>',calc)
number.bind('<Escape>',clear)
root.bind('<Button-1>',push_key)
root.wait_window()
fonzali 0 Light Poster

hi pytony , I appreciate if you explain to me how the keys in the image corespond to my keyboard keys , eg, if I click on button 3 on the image , 3 will appear , and ,this code is way over my head , more explanation on each def is greately appreciated thanks

TrustyTony 888 pyMod Team Colleague Featured Poster

By reducing margins and dividing by spacing:

x,y = ((e.x - 38) // 32, (e.y - 110) // 31)
fonzali 0 Light Poster

can you explain how you got those numbers?

TrustyTony 888 pyMod Team Colleague Featured Poster

By printing the values when moving the mouse to corners.

fonzali 0 Light Poster

ok thanks

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.