Wednesday, April 10, 2013

Resistor Calculator wxPython

So recently I decided to take a serious attempt at programming with using a GUI. While it has not been easy, the results are very satisfying. This program was entirely from scratch to get my feet wet, but I think my next project will be converting an already existing program to a GUI.

I chose wx over Tkinter for a couple reasons. The first was wx looks native while Tkinter looks like a Java application. Also, much like a Java application, however the windows are made with Tkinter, they have a bad habit of glitching out and making pieces illegible. The downside to wx is that it is not included with Python, however the wx packages are easy enough to get a hold of (in many different languages) and easy to install.

I'm happy with the end result, both the GUI and the code. I spent some extra time afterwards cleaning up the code, expanding variable names and commenting. This proved useful since I deleted a bunch of code that didn't do anything. Granted it's not perfect, I'm happy that overall it works.

You can check it out on my site here, or just copy/paste the source code.

#!/usr/bin/env python
# Author: Matthew DeSantis
#
# Resistor Calc

import wx

# Colors aranged by value (index=value)
COLOR = [
    'black',
    'brown',
    'red',
    'orange',
    'yellow',
    'green',
    'blue',
    'violet',
    'grey',
    'white']

# Colors because the default ones suck
COLORCODE = {
    "black":    "#000000",
    "brown":    "#a52a2a",
    "red":      "#ff0000",
    "orange":   "#ffa500",
    "yellow":   "#ffff00",
    "green":    "#008000",
    "blue":     "#0000ff",
    "violet":   "#800080",
    "grey":     "#808080",
    "white":    "#ffffff",
    "silver":   "#c0c0c0",
    "gold":     "#d4a017"}

# Multipliers in a dictionary, organized by value for ease of reading
MULTIPLIER = {
    1e-2:   'silver',
    1e-1:   'gold',
    1e0:    'black',
    1e1:    'brown',
    1e2:    'red',
    1e3:    'orange',
    1e4:    'yellow',
    1e5:    'green',
    1e6:    'blue',
    1e7:    'violet',}

# Converts a number to a list of resistor colors of equivalent value
def getColors(num):
    # To-be return value
    returnVals = []
    failReturnVals = [COLOR[0], COLOR[0], MULTIPLIER[1e0]]
    # Process the number
    indexPlaceholder = 0
    numStr = str(num)
    bandValues = ""

    # For anything smaller than 0
    if num < 1 and num >= 0.01:
        while indexPlaceholder < len(numStr):
            # Discard leading 0's and decimal
            if numStr[indexPlaceholder] == "0" or numStr[indexPlaceholder] == ".":
                indexPlaceholder += 1
            else:
                break

        # It's all zeroes, time to leave
        else:
            return failReturnVals

        bandValues = numStr[indexPlaceholder:]

        # Short a value, needs a black band
        if len(bandValues) < 2:
            bandValues = bandValues + "0"

        # Ready return color values
        before = True

        for value in bandValues:
            if len(returnVals) < 2:
                returnVals.append(COLOR[int(value)])
            elif value != "0":
                ret.append(COLOR[int(value)])
            else:
                break

    # And everything else
    elif num >= 1 and num <= 9990000000:
        indexPlaceholder = 2
        bandValues = numStr[:indexPlaceholder]

        # If there's 4 stripes
        if (num / float(bandValues)) % 10:
            indexPlaceholder = 3
            bandValues = numStr[:indexPlaceholder]

        # If there's a third band to add
        if len(numStr) > indexPlaceholder:
            if numStr[indexPlaceholder] != "0":
                bandValues = numStr[:indexPlaceholder + 1]

        # Needs another black band
        if len(bandValues) < 2:
            bandValues = bandValues + "0"

        for value in bandValues:
            if value == ".":
                continue

            returnVals.append(COLOR[int(value)])

    # Outside the limit
    else:
        return failReturnVals

    # Get the multiplier
    returnVals.append(MULTIPLIER[round(num / float(bandValues.replace(".", "")), 2)])

    return returnVals

#List of colors to Ohms
def getOhms(colors):
    bandValues = ""
    # Key storage
    multiply = 0

    # Get the index numbers of the colors
    while len(colors) > 1:
        bandValues = bandValues + str(COLOR.index(colors.pop(0)))

    # Find the key (multiplier) based on color (no easy way to do this)
    for key, value in MULTIPLIER.items():
        if value == colors[0]:
            multiply = key
            break

    # Color value * multiplier
    return float(bandValues) * multiply

def main():
    # Initialize app
    app = wx.App()
    frame = Window(None, "Resistor Calc")
    frame.display()
    app.MainLoop()

# The GUI class setup
class Window(wx.Frame):
    def __init__(self, parent, title):
        # Initialize the frame
        super(Window, self).__init__(
            parent,
            title=title,
            style=wx.DEFAULT_FRAME_STYLE,
            size=(550, 200))

        # UI setup
        self.uiInit()
        self.evtInit()
        self.colorSet()

        # Control variables
        self.lock = False
        self.prvs = self.ohmstxt.GetValue()

    def uiInit(self):
        # Third band list (needs optional added)
        self.r3colors = COLOR[:]
        self.r3colors.insert(0, "-")

        # Fourth band has some extra as well (organize by values)
        self.r4colors = COLOR[:-2]
        self.r4colors.insert(0, "gold")
        self.r4colors.insert(0, "silver")

        # Create panel and sub panels for colored boxes
        self.panel = wx.Panel(self)

        self.pr1 = wx.Panel(self.panel)
        self.pr2 = wx.Panel(self.panel)
        self.pr3 = wx.Panel(self.panel)
        self.pr4 = wx.Panel(self.panel)

        # Initialize font
        font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
        font.SetPointSize(12)

        # Boxes for resizing and aligning
        # 3 tall
        self.vbox = wx.BoxSizer(wx.VERTICAL)
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        hbox3 = wx.BoxSizer(wx.HORIZONTAL)

        # First row
        lblohms = wx.StaticText(self.panel, label="Ohms:")
        lblohms.SetFont(font)

        self.ohmstxt = wx.TextCtrl(self.panel, value="0.0")

        # Second row
        lblcolor = wx.StaticText(self.panel, label="Colors:")
        lblcolor.SetFont(font)

        self.r1 = wx.ComboBox(
            self.panel,
            choices=COLOR,
            style=wx.CB_READONLY)

        self.r2 = wx.ComboBox(
            self.panel,
            choices=COLOR,
            style=wx.CB_READONLY)

        self.r3 = wx.ComboBox(
            self.panel,
            choices=self.r3colors,
            style=wx.CB_READONLY)

        self.r4 = wx.ComboBox(
            self.panel,
            choices=self.r4colors,
            style=wx.CB_READONLY)

        # Create first row
        hbox1.Add(lblohms, flag=wx.RIGHT, border=8)
        hbox1.Add(self.ohmstxt, proportion=1)

        # Create second row
        hbox2.AddMany([
            (lblcolor, wx.RIGHT, 8),
            (self.r1,),
            (self.r2,),
            (self.r3,),
            (self.r4,)])

        # Create third row
        hbox3.AddMany([
            (self.pr1, 1, wx.EXPAND),
            (self.pr2, 1, wx.EXPAND),
            (self.pr3, 1, wx.EXPAND),
            (self.pr4, 1, wx.EXPAND)])

        # Insert rows with padding
        self.vbox.Add(hbox1, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10)
        self.vbox.Add((-1, 10))
        self.vbox.Add(hbox2, flag=wx.EXPAND|wx.LEFT|wx.RIGHT, border=10)
        self.vbox.Add((-1, 10))
        self.vbox.Add(hbox3, 1, flag=wx.EXPAND|wx.LEFT|wx.RIGHT, border=10)
        self.vbox.Add((-1, 10))

        # Sets everything in place on the window
        self.panel.SetSizer(self.vbox)

        # Set combobox selections (not intialized by default
        self.r1.SetSelection(0)
        self.r2.SetSelection(0)
        self.r3.SetSelection(0)
        self.r4.SetSelection(2)

    # Initialize events
    def evtInit(self):
        self.Bind(wx.EVT_COMBOBOX, self.onCBChange)
        self.Bind(wx.EVT_TEXT, self.onTextChange)

    # Combobox was changed
    def onCBChange(self, e):
        self.lock = True

        bandColors = []
        thirdBand = self.r3.GetValue()

        bandColors.append(self.r1.GetValue())
        bandColors.append(self.r2.GetValue())

        # Third band is optional, check to see if it's actually used
        if thirdBand != "-":
            bandColors.append(thirdBand)

        bandColors.append(self.r4.GetValue())

        self.ohmstxt.SetValue(str(getOhms(bandColors)))
        self.colorSet()

    # Text change
    def onTextChange(self, e):
        ohms = self.ohmstxt.GetValue()

        # This is because event fires twice for some unknown reason
        if self.lock or ohms == self.prvs:
            self.prvs = ohms
            self.lock = False
            return None

        # If no value given, default to zero
        if not ohms:
            ohms = "0"

        try:
            bandColors = getColors(float(ohms))
            self.r1.SetSelection(COLOR.index(bandColors[0]))
            self.r2.SetSelection(COLOR.index(bandColors[1]))

            # All 4 bands are present
            if len(bandColors) > 3:
                self.r3.SetSelection(self.r3colors.index(bandColors[2]))
                self.r4.SetSelection(self.r4colors.index(bandColors[3]))
            # Third band is not present
            else:
                self.r3.SetSelection(0)
                self.r4.SetSelection(self.r4colors.index(bandColors[2]))

            self.colorSet()

        # Not a valid number or possible value
        except (ValueError, KeyError):
            pass

    # Display the colors for the bands
    def colorSet(self):
        self.pr1.SetBackgroundColour(COLORCODE[self.r1.GetValue()])
        self.pr2.SetBackgroundColour(COLORCODE[self.r2.GetValue()])

        # If band 3 isn't used, don't show it
        if self.r3.GetValue() == "-":
            self.pr3.SetBackgroundColour(wx.NullColour)
        else:
            self.pr3.SetBackgroundColour(COLORCODE[self.r3.GetValue()])

        self.pr4.SetBackgroundColour(COLORCODE[self.r4.GetValue()])

    def display(self):
        self.Centre
        self.Show()

if __name__ == '__main__':
    main()

No comments:

Post a Comment

Tag Cloud

.NET (2) A+ (5) ad ds (1) addon (4) Android (4) anonymous functions (1) application (9) arduino (1) artificial intelligence (1) backup (1) bash (6) camera (2) certifications (3) comptia (5) css (2) customize (11) encryption (3) error (13) exploit (5) ftp (1) funny (4) gadget (4) games (3) GUI (5) hardware (16) haskell (6) help (14) HTML (3) imaging (2) irc (1) it (1) java (2) javascript (13) jobs (1) Linux (19) lua (1) Mac (4) malware (1) math (6) msp (1) network (13) perl (2) php (3) plugin (2) powershell (8) privacy (2) programming (24) python (10) radio (2) regex (3) repair (2) security (16) sound (2) speakers (2) ssh (1) story (5) Techs from the Crypt (5) telnet (1) tools (13) troubleshooting (11) tutorial (9) Ubuntu (4) Unix (2) virtualization (2) web design (6) Windows (16) world of warcraft (1) wow (1) wx (1)