Code sharing

Joined
Dec 10, 2022
Messages
108
Reaction score
26
I don't see any forum for code sharing and feedback so, I'll post here.
Was helping someone with an exercise creating an overlay with tkinter and python. Their project was to sound an alarm when the countdown reaches 10 seconds. After countdown reaches 0, it resets. This is what I came up with. I added key binds although their project did not call for them.

I welcome any thoughts and feedback.

Keybinds:
  • shift+r = reset
  • pause = stop/start counter
  • esc = exit

Python:
import tkinter as tk
import pygame
from pathlib import Path
from os.path import exists


# Get path to executing script
path = Path(__file__).parent

# Initialize pygame.mixer
pygame.mixer.init()

class Sound:
    ''' Sound class loads, plays, and stops sound file '''
    def __init__(self, audio=''):
        self.audio = audio
        self.verify = False
        if self.audio and exists(f'{path}/{self.audio}'):
            pygame.mixer.music.load(f'{path}/{self.audio}')
            self.verify = True

    def play(self):
        ''' Method for playing sound file '''
        if self.verify:
            pygame.mixer.music.play()

    def stop(self):
        ''' Method to stop playing sound file '''
        if self.verify:
            pygame.mixer.music.stop()


class DragIt:
    ''' DragIt class takes a widget and moves it around the desktop '''
    def __init__(self, widget):
        self.widget = widget

        # Mouse binds
        widget.bind('<1>', self.grab)
        widget.bind('<B1-Motion>', self.move)
        widget.configure(cursor='hand1')
        widget.bind('<ButtonRelease>', self.reset)

    def grab(self, event):
        ''' Method for getting start position '''
        self.widget.configure(cursor='hand2')
        self.start_x = event.x
        self.start_y = event.y

    def move(self, event):
        ''' Method for moving widget '''
        dx = event.x - self.start_x
        dy = event.y - self.start_y

        left = self.widget.winfo_x() + dx
        top = self.widget.winfo_y() + dy

        self.widget.geometry(f'+{left}+{top}')

    def reset(self, event):
        ''' Method resets cursor pointer '''
        self.widget.configure(cursor='hand1')


class Timer:
    ''' Timer class sets a countdown timer - default is 2 minutes '''
    def __init__(self, duration=120):
        self.duration = duration+1

    def update(self):
        ''' Method for updating count '''
        self.duration -= 1
        hours = divmod(self.duration, 3600)
        minutes = divmod(hours[1], 60)
        seconds = divmod(minutes[1], 60)

        return f'{hours[0]:02}:{minutes[0]:02}:{seconds[1]:02}'


class Window:
    ''' Window class is for displaying window '''
    def __init__(self, parent):
        parent.geometry('+50+50')
        parent.minsize(130,30)
        parent.maxsize(130,30)
        parent.wait_visibility(parent)
        parent.wm_attributes('-topmost', True)
        parent.wm_attributes('-alpha', 0.5)
        parent.wm_attributes('-type', 'splash')
        parent.focus_force()

        self.parent = parent

        self.label = tk.Label(parent, anchor='w', padx=10)
        self.label.pack(fill='x', expand=True)
        self.label.configure(font=(None, 18, 'normal'))


class Controller:
    ''' Controller class handles communications between Timer and Window class '''
    def __init__(self, window, timer):
        # Create instance variables
        self.window = window
        self.timer = timer
        self.action = False
        self.duration = self.timer.duration

        # Create the alarm sound
        # Path to sound file example media/myalarm.mp3
        # If in the same directory as script, just filename.mp3 will work
        # This can also be empty for no sound or if file not found
        # should still work
        self.alarm = Sound()

        # Make the window draggable
        widget = DragIt(self.window.parent)

        # Key binds
        self.window.parent.bind('<R>', self.reset)
        self.window.parent.bind('<Pause>', self.pause)
        self.window.parent.bind('<Escape>', lambda event: self.window.parent.destroy())

        # Get it started
        self.update()

    def update(self):
        ''' Method updates thw window '''
        self.window.label.configure(text=self.timer.update())       

        # If counter is 10 or below, play alarm and change bgcolor
        if self.timer.duration <= 10:
            self.alarm.play()
            if self.timer.duration % 2 == 0:
                self.window.label.configure(bg='red', fg='white')
            else:
                self.window.label.configure(bg='orange', fg='red')

        # If timer reaches 0 - reset
        # I tried calling the reset function here but, caused problems with counter
        if self.timer.duration <= 0:
            self.timer.duration = self.duration
            self.alarm.stop()
            self.window.label.configure(bg='gray86', fg='black')

        # Call .after to update window
        self.updating = self.window.parent.after(1000, self.update)

    def reset(self, event):
        ''' Method for resetting everything - key bind is shift+r '''
        self.window.parent.after_cancel(self.updating)
        self.timer.duration = self.duration
        self.action = False
        self.window.label.configure(bg='gray86', fg='black')
        self.alarm.stop()
        self.update()

    def pause(self, event):
        ''' Method for pausing counter '''
        self.action = True if not self.action else False
        if self.action:
            self.window.parent.after_cancel(self.updating)
            self.timer.duration = self.timer.duration
            self.alarm.stop()
        else:
            self.update()


if __name__ == '__main__':
    root = tk.Tk()
    # Timer excepts milaseconds - example 120 = 2 minutes (60x2 = 120 milaseconds = 2 minutes)
    # If Timer left blank will default to 2 minutes
    controller = Controller(Window(root), Timer(15))
    root.mainloop()

 
Last edited:
Joined
Sep 21, 2022
Messages
189
Reaction score
26
Sharing code and getting feedback is a good idea, I hope that you and more people continue to do it.

I'm not a Python programmer, so I can't tell good Python style from poor Python style.

From a general perspective, this program is very long, for what it does.

In the old days, if I typed in a program this long, from a magazine, I'd expect it to play backgammon.
 
Joined
Dec 10, 2022
Messages
108
Reaction score
26
Here is one more. I tested this with using a crontab. Would work great for a popup reminder.

Python:
import tkinter as tk
from random import randrange, uniform, choice
from time import time

class Bubble(tk.Tk):
    ''' Class creates popup window '''
    def __init__(self, *args, text='Default Text', **kwargs):
        super().__init__(*args, **kwargs)

        # Instance variables removes titlebar and grabs focus of window
        self.wait_visibility(self)
        self.wm_attributes('-topmost', True)
        self.wm_attributes('-alpha', 0.9)
        self.wm_attributes('-type', 'splash')
        self.focus_force()

        # Get time window opens, set duration for window, and set opacity of window
        self.start = time()
        self.duration = 5
        self.opacity = float(0.9)

        # Just for picking random background color
        colors = ['#fffdd0', '#ffa07a', '#ffcba4', '#a6e7ff', '#e6e8fa']

        self.label = tk.Label(self, text=text, padx=10, pady=10, bg=choice(colors))
        self.label.pack(fill='x', expand=True)
        self.label.configure(
            wraplength = 300,
            justify = 'left',
            font = (None, 14, 'normal')
        )

        # get window info for positioning of window
        self.update()

        # Calculations for window geometry
        x = self.winfo_screenwidth()//2, self.winfo_screenwidth()-int(self.winfo_screenwidth())
        self.xpos = randrange(x[0], self.winfo_screenwidth()-int(self.label.winfo_width()*0.9))
        
        self.ypos = int(self.winfo_screenheight()*uniform(0.2, 0.4))
        
        # Set window geometry
        self.geometry(f'{self.label.winfo_width()}x{self.label.winfo_height()}+{self.xpos}+{self.ypos}')

        # Start timer and mainloop
        self.timer()
        self.mainloop()
        


    def timer(self):
        ''' Method for countdown timer '''
        counter = self.duration + self.start - time()
        seconds = int(divmod(counter, 60)[1])

        # Call timer every one second
        self._timer = self.after(1000, self.timer)

        if seconds == 1:
            self.fade()

    def move(self):
        ''' Method for moving bubble up '''
        self.ypos -= 25

    def fade(self):
        ''' Method for creating fade and move up effect '''
        self.opacity -= float(0.1)
        self.wm_attributes('-alpha', self.opacity)
        disolve = self.after(100, self.fade)
        up = self.after(0, self.move)
        self.geometry(f'{self.label.winfo_width()}x{self.label.winfo_height()}+{self.xpos}+{self.ypos}')

        if self.opacity <= float(0.0):
            self.after_cancel(self._timer)
            self.after_cancel(disolve)
            self.after_cancel(up)
            self.destroy()

messages = [
    'Hello World! This is a random messages.',
    'The skys are blue and the clouds are white and fluffy.',
    'I look at you and my blood boils hot, I feel my temperature rise.',
    'Who\'s that knocking at the door? Is it you again? You can love me tonight, \
if you want. In the moring make sure your gone.'
]

# Create five popups for display purposes
for i in range(5):
    t = Bubble(text=choice(messages))

 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,056
Messages
2,570,446
Members
47,097
Latest member
MarionMajo

Latest Threads

Top