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 (tryItOnline):

# 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 (tryItOnline):

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 algebraic 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 (tryItOnline):

# 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 (tryItOnline):

# 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 (tryItOnline):

# 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!!!