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