pyTermTk - Your first Calculator

Intro

This example shows how to use signals and slots to implement the functionality of a calculator widget, and how to use TTkGridLayout to place child widgets in a grid. Due to the modular nature of pyTermTk, the same result may be achieved in multiple ways, for the sack of simplicity I will use a procedural approach avoiding to create a calculator widget.

Design

First of all we need a rough idea about the layout we want to achieve. Thanks to my amazing paint.py I draw my idea and I used it to check the grid placement of any widget

Col:  0      1       2       3
 |-------|-------|-------|-------|   Row:
╔═════════════════════════════════╗ ---
║ ┌─────────────────────────────┐ ║  |
║ │ r:0,c:0,  rspan:1, cspan:4  │ ║  | 0
║ └─────────────────────────────┘ ║ ---
║ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ║  |
║ │ 1,0 │ │ 1,1 │ │ 1,2 │ │ 1,3 │ ║  | 1
║ └─────┘ └─────┘ └─────┘ └─────┘ ║ ---
║ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ║  |
║ │ 2,0 │ │ 2,1 │ │ 2,2 │ │ 2,3 │ ║  | 2
║ └─────┘ └─────┘ └─────┘ └─────┘ ║ ---
║ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ║  |
║ │ 3,0 │ │ 3,1 │ │ 3,2 │ │ 3,3 │ ║  | 3
║ └─────┘ └─────┘ └─────┘ │     │ ║ ---
║ ┌─────────────┐ ┌─────┐ │ 2,1 │ ║  |
║ │ 4,0   1,2   │ │ 4,2 │ │     │ ║  | 4
║ └─────────────┘ └─────┘ └─────┘ ║ ---
╚═════════════════════════════════╝

Start Coding

Initialize the window

From calculator.001.py

# If you want to try without installation, run from the pyTermTk root folder
PYTHONPATH=`pwd` tutorial/calculator/calculator.001.py

First thing first I need a parent widget with a grid layout that I can use to place the elements of my calculator

import TermTk as ttk

    # Create a root object (it is a widget that represent the terminal)
root = ttk.TTk()

    # Create a window and attach it to the root (parent=root)
calculatorWin = ttk.TTkWindow(parent=root, pos=(1,1), size=(30,17), title="My first Calculator")

    # Create a grid layout and set it as default for the window
winLayout = ttk.TTkGridLayout()
calculatorWin.setLayout(winLayout)

Once we have out layout object (winLayout) ready we can add all the widgets of calculator to it

Add all the widgets of calculator to it

From calculator.002.py

Based on the positions and sizes defined in the design layout, I place all the widgets on the TTkGridLayout (winLayout)

# If you want to try without installation, run from the pyTermTk root folder
PYTHONPATH=`pwd` tutorial/calculator/calculator.002.py
    # Define the Label and attach it to the grid layout at
    # Position (Row/Col) (0,0) and (Row/Col)Span (1,4)
    # I force the Max Height to 1 in order to avoid this widget to resize vertically
resLabel = ttk.TTkLabel(text="Results", maxHeight=1)
winLayout.addWidget(resLabel, 0,0, 1,4)

    # Define the Numeric Buttons and attach them to the grid layout
btn1 = ttk.TTkButton(border=True, text="1")
btn2 = ttk.TTkButton(border=True, text="2")
btn3 = ttk.TTkButton(border=True, text="3")
btn4 = ttk.TTkButton(border=True, text="4")
btn5 = ttk.TTkButton(border=True, text="5")
btn6 = ttk.TTkButton(border=True, text="6")
btn7 = ttk.TTkButton(border=True, text="7")
btn8 = ttk.TTkButton(border=True, text="8")
btn9 = ttk.TTkButton(border=True, text="9")

winLayout.addWidget(btn1, 1,0) # Colspan/Rowspan are defaulted to 1 if not specified
winLayout.addWidget(btn2, 1,1)
winLayout.addWidget(btn3, 1,2)
winLayout.addWidget(btn4, 2,0)
winLayout.addWidget(btn5, 2,1)
winLayout.addWidget(btn6, 2,2)
winLayout.addWidget(btn7, 3,0)
winLayout.addWidget(btn8, 3,1)
winLayout.addWidget(btn9, 3,2)

    # Adding the "0" button on the bottom which alignment is
    # Position (Row/Col) (4,0) (Row/Col)span (1,2)
    # Just to show off I am using another way to attach it to the grid layout
winLayout.addWidget(btn0:=ttk.TTkButton(border=True, text="0"), 4,0, 1,2)

    # Define the 2 algebric buttons
winLayout.addWidget(btnAdd:=ttk.TTkButton(border=True, text="+"), 1,3)
winLayout.addWidget(btnSub:=ttk.TTkButton(border=True, text="-"), 2,3)

    # The Enter "=" button (2 rows wide )
winLayout.addWidget(btnRes:=ttk.TTkButton(border=True, text="="), 3,3, 2,1)

    # Last but not least an extrabutton just for  fun
winLayout.addWidget(mysteryButton:=ttk.TTkButton(border=True, text="?"), 4,2)

This code will produce this result:

╔════════════════════════════╗
║ My first Calculator        ║
╟────────────────────────────╢
║Results                     ║
║┌─────┐┌─────┐┌─────┐┌─────┐║
║│  1  ││  2  ││  3  ││  +  │║
║╘═════╛╘═════╛╘═════╛╘═════╛║
║┌─────┐┌─────┐┌─────┐┌─────┐║
║│  4  ││  5  ││  6  ││  -  │║
║╘═════╛╘═════╛╘═════╛╘═════╛║
║┌─────┐┌─────┐┌─────┐┌─────┐║
║│  7  ││  8  ││  9  ││     │║
║╘═════╛╘═════╛╘═════╛│  =  │║
║┌────────────┐┌─────┐│     │║
║│     0      ││  ?  ││     │║
║╘════════════╛╘═════╛╘═════╛║
╚════════════════════════════╝

Cool isn’t it?

Numeric Button Events

From calculator.003.py

# If you want to try without installation, run from the pyTermTk root folder
PYTHONPATH=`pwd` tutorial/calculator/calculator.003.py
    # I am defining a simlpe structure that can be used to store
    # the mathematical elements of the formulae
mathElements = {'a':None, 'b':None, 'operation':None}

    # This is a simple callback that I can use to store the numbers
    # I didn't include extra logic because out of the scope of this tutorial
def setFactor(value):
    if mathElements['operation'] is None:
        mathElements['a'] = mathElements['a']*10+value
        # Display the value in the label
        resLabel.setText(f"{mathElements['a']}")
    else:
        mathElements['b'] = mathElements['b']*10+value
        # Display the value in the label
        resLabel.setText(f"{mathElements['b']}")

    # I am using a lambda function to redirect the click event to the
    # proper "setFactor" callback, this is due to the fact that the
    # "clicked" signal does not return any object or information that
    # can be used to identify which button has been pressed
    # different approaches are possible, i.e. create a separate function
    # for each button
btn0.clicked.connect(lambda : setFactor(0))
btn1.clicked.connect(lambda : setFactor(1))
btn2.clicked.connect(lambda : setFactor(2))
btn3.clicked.connect(lambda : setFactor(3))
btn4.clicked.connect(lambda : setFactor(4))
btn5.clicked.connect(lambda : setFactor(5))
btn6.clicked.connect(lambda : setFactor(6))
btn7.clicked.connect(lambda : setFactor(7))
btn8.clicked.connect(lambda : setFactor(8))
btn9.clicked.connect(lambda : setFactor(9))

Operation and results events

From calculator.004.py

# If you want to try without installation, run from the pyTermTk root folder
PYTHONPATH=`pwd` tutorial/calculator/calculator.004.py
    # Define 2 slots to handle the Add and Sub operations
@pyTTkSlot()
def setOperationAdd():
    mathElements['operation'] = "ADD"

@pyTTkSlot()
def setOperationSub():
    mathElements['operation'] = "SUB"

    # Connect them to the clicked signal of the buttons
btnAdd.clicked.connect(setOperationAdd)
btnSub.clicked.connect(setOperationSub)

    # Same for the "=" button
@pyTTkSlot()
def executeOperation():
    if mathElements['operation'] is not None:
        if mathElements['operation'] == "ADD":
            res = mathElements['a'] + mathElements['b']
            resLabel.setText(f"{mathElements['a']} + {mathElements['b']} = {res}")
        else: # "SUB" Routine
            res = mathElements['a'] - mathElements['b']
            resLabel.setText(f"{mathElements['a']} - {mathElements['b']} = {res}")
        # reset the values
        mathElements['a'] = res
        mathElements['b'] = 0
        mathElements['operation'] = None

btnRes.clicked.connect(executeOperation)

Beware the Mystery Button

From calculator.005.py

# If you want to try without installation, run from the pyTermTk root folder
PYTHONPATH=`pwd` tutorial/calculator/calculator.005.py
@pyTTkSlot()
def showAboytWindow():
    # I am using the overlay helper to show the
    # About window on top of the screen
    # it will be closed once the focus is lost
    ttk.TTkHelper.overlay(mysteryButton, ttk.TTkAbout(), -2, -1)

mysteryButton.clicked.connect(showAboytWindow)

Press the Mystery “?” Button if you dare!!!

╔═══════════════════════════════════════════╗
║ My first Calculator                       ║
╟───────────────────────────────────────────╢
║1 + 2 = 3                                  ║
║┌─────────┐┌────────┐┌─────────┐┌─────────┐║
║│    1    ││   2    ││    3    ││    +    │║
║╘═════════╛╘════════╛╘═════════╛╘═════════╛║
║┌─────────┐┌────────┐┌─────────┐┌─────────┐║
║│    4    ││   5    ││    6    ││    -    │║
║│         ││  ╔═════════════════════════════════════════════════════╗
║╘═════════╛╘══║ About...                                            ║
║┌─────────┐┌──╟─────────────────────────────────────────────────────╢
║│    7    ││  ║   ▝▀▄           ████████╗            ████████╗      ║
║│         ││  ║ ▗▄▀▜▀▘▄▄▖       ╚══██╔══╝            ╚══██╔══╝      ║
║╘═════════╛╘══║▐▐▛▄▐▀▌▝▘▀          ██║  ▄▄  ▄ ▄▄ ▄▄▖▄▖  ██║ █ ▗▖    ║
║┌─────────────║▝▀▌▜▝▀▘▌▌   ▞▀▚ ▖▗  ██║ █▄▄█ █▀▘  █ █ █  ██║ █▟▘     ║
║│         0   ║ ▗▗▞▜▀▌▗▌▖  ▙▄▞▐▄▟  ██║ ▀▄▄▖ █    █ ▝ █  ██║ █ ▀▄    ║
║│             ║ ▝▐▙▟▟▌▟▌▌  ▌    ▐  ╚═╝                  ╚═╝         ║
║╘═════════════║  ▐▐▌▗▌▘▌▌    ▚▄▄▘   Version: 0.7.0a16               ║
╚══════════════║  ▝▐▌▐▖▜▌▌                                           ║
               ║  ▝▐▀▝▘▀▘▘ Powered By, Eugenio Parodi                ║
               ║   ▝▀▀▀▀▘                                            ║
               ║ https://github.com/ceccopierangiolieugenio/pyTermTk ║
               ╚═════════════════════════════════════════════════════╝

Well, with colors is another thing!!!