Python Programming
Intermediate to Advanced Guide
A comprehensive resource for Python students
Table of Contents
1. Exception Handling
What are Exceptions?
Exceptions are events that occur during program execution that disrupt the normal flow of instructions. They arise from various issues like invalid input, file errors, network problems, or logical mistakes.
Exception Flow Diagram
Basic Exception Handling Structure
try:
# Code that might raise an exception
result = 10 / 0 # This will raise a ZeroDivisionError
except ZeroDivisionError:
# Code to handle the specific exception
print("Error: Division by zero!")
except (TypeError, ValueError) as e:
# Handle multiple exception types and capture the exception object
print(f"Error occurred: {e}")
except Exception as e:
# Catch any other exceptions that weren't caught above
print(f"Unexpected error: {e}")
else:
# Runs only if no exceptions were raised
print("Division operation successful!")
finally:
# Always executes, regardless of whether an exception occurred
print("Cleanup code here - always executes")
Memory Tip
Think of exception handling like a safety net in a circus:
- try = The daring acrobat attempting a trick
- except = The safety net that catches falls
- else = The celebration when the trick succeeds
- finally = The cleanup crew that comes in regardless
Remember the order with "TEEN": Try, Except, Else, aNd finally.
Common Built-in Exceptions
Exception | Description | Example |
---|---|---|
ZeroDivisionError | Raised when division by zero occurs | 10 / 0 |
TypeError | Raised when an operation is performed on an inappropriate type | '2' + 2 |
ValueError | Raised when a function receives an argument of the correct type but improper value | int('abc') |
FileNotFoundError | Raised when a file or directory is requested but doesn't exist | open('missing.txt') |
IndexError | Raised when a sequence index is out of range | my_list[99] |
KeyError | Raised when a dictionary key is not found | my_dict['missing'] |
Creating Custom Exceptions
You can create your own exception classes by inheriting from the built-in Exception class or any of its subclasses. Custom exceptions help make your code more readable and provide more specific error handling.
class InsufficientFundsError(Exception):
"""Exception raised when a withdrawal exceeds the available balance."""
def __init__(self, balance, amount, message="Insufficient funds"):
self.balance = balance
self.amount = amount
self.message = f"{message}: Tried to withdraw {amount} from balance of {balance}"
super().__init__(self.message)
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
return self.balance
# Using the custom exception
try:
account = BankAccount(100)
account.withdraw(150)
except InsufficientFundsError as e:
print(e) # This will print the custom error message
# Additional handling specific to this type of error
Best Practices
- Be specific with exception types - catch only what you can handle
- Use multiple except blocks for different types of exceptions
- Avoid bare except statements (without specifying the exception type)
- Use finally blocks for cleanup code like closing files or network connections
- Create custom exceptions for domain-specific errors
- Include useful information in exception messages
Context Managers with try-except
Context managers (using the with
statement) provide a clean way to handle resource acquisition and release,
reducing the need for try-finally blocks.
# Without context manager
try:
file = open('data.txt', 'r')
content = file.read()
# Process content
finally:
file.close() # Ensure the file is closed even if an exception occurs
# With context manager - much cleaner
try:
with open('data.txt', 'r') as file:
content = file.read()
# Process content
# File is automatically closed when the block exits
except FileNotFoundError:
print("The file does not exist")
except PermissionError:
print("You don't have permission to read this file")
2. Regular Expressions
What are Regular Expressions?
Regular expressions (regex or regexp) are powerful sequences of characters that define a search pattern.
They are used for pattern matching with strings and for find-and-replace operations.
In Python, regular expressions are implemented through the re
module.
Common Regex Patterns
Pattern | Description | Example |
---|---|---|
\d | Matches any digit (0-9) | \d+ matches "42", "123", etc. |
\w | Matches any alphanumeric character and underscore | \w+ matches "hello_world", "Python3", etc. |
\s | Matches any whitespace character | \s+ matches spaces, tabs, newlines |
[...] | Matches any character in the brackets | [aeiou] matches any vowel |
* | Matches 0 or more of the preceding character | ab*c matches "ac", "abc", "abbc", etc. |
+ | Matches 1 or more of the preceding character | ab+c matches "abc", "abbc", but not "ac" |
? | Matches 0 or 1 of the preceding character | colou?r matches "color" and "colour" |
{n} | Matches exactly n occurrences of the preceding character | \d{3} matches exactly 3 digits |
^ | Matches the start of a string | ^Python matches strings starting with "Python" |
$ | Matches the end of a string | Python$ matches strings ending with "Python" |
Memory Tip
Remember regex characters with these mnemonics:
- \d, \w, \s = "digits, words, spaces"
- *, +, ? = "any, at least one, maybe"
- ^ and $ = "starts and ends" (think of them as the bookends of your text)
- [...] = "shopping cart" (you're selecting specific items from many choices)
Basic Regex Functions in Python
import re
text = "Contact us at info@example.com or support@python.org"
# Search for the first match
match = re.search(r'\w+@\w+\.\w+', text)
if match:
print("First email found:", match.group()) # Output: info@example.com
# Find all matches
all_emails = re.findall(r'\w+@\w+\.\w+', text)
print("All emails:", all_emails) # Output: ['info@example.com', 'support@python.org']
# Replace matches
new_text = re.sub(r'\w+@\w+\.\w+', '[EMAIL REDACTED]', text)
print("Redacted text:", new_text)
# Output: Contact us at [EMAIL REDACTED] or [EMAIL REDACTED]
# Split by pattern
parts = re.split(r'[@\.]', 'info@example.com')
print("Split result:", parts) # Output: ['info', 'example', 'com']
# Using match to check if pattern is at the beginning of the string
if re.match(r'Contact', text):
print("Text starts with 'Contact'") # This will print
Regex Groups and Capture
Regex groups allow you to extract specific parts of a match using parentheses. They're useful for parsing structured text.
import re
# Parsing a date in format MM-DD-YYYY
date_string = "Today's date is 12-25-2023"
date_pattern = r'(\d{2})-(\d{2})-(\d{4})'
match = re.search(date_pattern, date_string)
if match:
month, day, year = match.groups()
print(f"Month: {month}, Day: {day}, Year: {year}")
# Output: Month: 12, Day: 25, Year: 2023
# Access specific group by number
print(f"Year: {match.group(3)}") # Output: Year: 2023
# The entire match
print(f"Full date: {match.group(0)}") # Output: Full date: 12-25-2023
# Named groups for better readability
pattern = r'(?P\d{2})-(?P\d{2})-(?P\d{4})'
match = re.search(pattern, date_string)
if match:
print(f"Using named groups - Month: {match.group('month')}, Year: {match.group('year')}")
# Output: Using named groups - Month: 12, Year: 2023
Common Regex Use Cases
import re
# Email validation
def is_valid_email(email):
pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
return bool(re.match(pattern, email))
print(is_valid_email("user@example.com")) # True
print(is_valid_email("invalid-email")) # False
# Phone number formatting (US format)
def format_phone_number(number):
# Remove all non-digit characters
digits = re.sub(r'\D', '', number)
if len(digits) == 10:
# Format as (XXX) XXX-XXXX
return re.sub(r'(\d{3})(\d{3})(\d{4})', r'(\1) \2-\3', digits)
return "Invalid phone number"
print(format_phone_number("1234567890")) # (123) 456-7890
print(format_phone_number("123-456-7890")) # (123) 456-7890
# URL extraction
def extract_urls(text):
pattern = r'https?://[\w\.-]+\.\w+[\w/\.-]*'
return re.findall(pattern, text)
text = "Visit our website at https://www.example.com or https://python.org for more info"
print(extract_urls(text)) # ['https://www.example.com', 'https://python.org']
# Password validation
def is_strong_password(password):
# Check for minimum length (8 characters)
if len(password) < 8:
return False
# Check for at least one lowercase letter
if not re.search(r'[a-z]', password):
return False
# Check for at least one uppercase letter
if not re.search(r'[A-Z]', password):
return False
# Check for at least one digit
if not re.search(r'\d', password):
return False
# Check for at least one special character
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
return False
return True
print(is_strong_password("Weak123")) # False
print(is_strong_password("StrongP@ssword123")) # True
Best Practices
- Use raw strings (
r"..."
) for regex patterns to avoid backslash confusion - Compile regex patterns that are used multiple times with
re.compile()
for better performance - Use named groups (
(?P<name>pattern)
) for complex patterns to improve readability - Test regex patterns with tools like regex101.com before implementing them
- Keep patterns as simple as possible for maintainability
- Use regex comments with the
re.VERBOSE
flag for complex patterns
Regex Flags
Regex flags modify how the pattern is interpreted and matched. They can be specified as an optional parameter to most regex functions.
Flag | Description | Example |
---|---|---|
re.IGNORECASE or re.I | Case-insensitive matching | re.search('python', 'PYTHON', re.I) |
re.MULTILINE or re.M | ^ and $ match at beginning/end of each line | re.findall('^start', text, re.M) |
re.DOTALL or re.S | . matches newline characters as well | re.search('start.*end', text, re.S) |
re.VERBOSE or re.X | Allows you to write clearer patterns with comments and whitespace | See example below |
import re
# Complex pattern for email with comments
email_pattern = re.compile(r'''
^ # Start of string
[\w\.-]+ # Username (alphanumeric, dots, hyphens)
@ # @ symbol
[\w\.-]+ # Domain name
\. # Dot
[a-zA-Z]{2,} # Top-level domain (2+ letters)
$ # End of string
''', re.VERBOSE)
# Test the pattern
emails = ['user@example.com', 'invalid@email', 'another.user@sub.domain.co.uk']
for email in emails:
if email_pattern.match(email):
print(f"{email} is valid")
else:
print(f"{email} is invalid")
# Multiple flags can be combined using the | operator
pattern = re.compile(r'python', re.IGNORECASE | re.MULTILINE)
3. Working with APIs
What are APIs?
API (Application Programming Interface) is a set of rules and protocols that allows different software applications to communicate with each other. Web APIs enable programmatic access to services and data from external systems over the internet.
API Communication Flow
Making API Requests with Python
The most popular library for making HTTP requests in Python is requests
. It simplifies working with APIs by providing a clean, intuitive interface.
import requests
# Basic GET request
response = requests.get('https://api.example.com/data')
print(f"Status code: {response.status_code}")
print(f"Response content: {response.text}")
# GET request with parameters
params = {
'q': 'python',
'limit': 10
}
response = requests.get('https://api.example.com/search', params=params)
print(f"URL with parameters: {response.url}")
# POST request with JSON data
data = {
'name': 'John Doe',
'email': 'john@example.com'
}
response = requests.post('https://api.example.com/users', json=data)
# Request with headers (e.g., for authentication)
headers = {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
}
response = requests.get('https://api.example.com/protected-resource', headers=headers)
# Other HTTP methods
put_response = requests.put('https://api.example.com/users/1', json={'name': 'Updated Name'})
delete_response = requests.delete('https://api.example.com/users/1')
Handling API Responses
import requests
# Make a request to a JSON API
response = requests.get('https://jsonplaceholder.typicode.com/posts/1')
# Check if the request was successful
if response.status_code == 200:
# Parse JSON response
data = response.json()
print(f"Post title: {data['title']}")
print(f"Post body: {data['body']}")
else:
print(f"Error: {response.status_code}")
print(f"Error message: {response.text}")
# Working with response headers
print(f"Content type: {response.headers['Content-Type']}")
print(f"Response headers: {response.headers}")
# Getting binary content (e.g., for downloading images)
image_response = requests.get('https://example.com/image.jpg')
if image_response.status_code == 200:
with open('downloaded_image.jpg', 'wb') as f:
f.write(image_response.content)
print("Image downloaded successfully")
# Handling different status codes
def handle_response(response):
status = response.status_code
if status == 200:
return response.json()
elif status == 400:
print("Bad request - check your parameters")
elif status == 401:
print("Unauthorized - authentication required")
elif status == 403:
print("Forbidden - you don't have permission")
elif status == 404:
print("Resource not found")
elif status == 429:
print("Too many requests - rate limit exceeded")
elif status >= 500:
print("Server error - try again later")
return None
Authentication Methods
APIs often require authentication to access protected resources. Here are common authentication methods:
Auth Method | Description | Implementation |
---|---|---|
API Key | Simple string provided as a parameter or header | headers = {'X-API-Key': 'your_api_key'} |
Basic Auth | Username and password encoded in base64 | requests.get(url, auth=('username', 'password')) |
Bearer Token | JWT or OAuth token provided in Authorization header | headers = {'Authorization': 'Bearer your_token'} |
OAuth 2.0 | Open standard for access delegation | Use requests-oauthlib library |
from requests_oauthlib import OAuth2Session
# OAuth2 client credentials flow example
client_id = 'your_client_id'
client_secret = 'your_client_secret'
token_url = 'https://provider.com/oauth2/token'
# Create OAuth2 session
oauth = OAuth2Session(client_id=client_id)
# Get token
token = oauth.fetch_token(
token_url=token_url,
client_id=client_id,
client_secret=client_secret
)
# Make authenticated request
response = oauth.get('https://api.example.com/resource')
print(response.json())
Error Handling and Retries
Working with external APIs requires robust error handling and retry strategies to handle temporary failures.
import requests
import time
from requests.exceptions import RequestException, Timeout, HTTPError
def fetch_with_retry(url, max_retries=3, backoff_factor=0.5, timeout=10):
"""
Make a GET request with retry logic.
Args:
url: The URL to request
max_retries: Maximum number of retry attempts
backoff_factor: Factor to determine exponential backoff time
timeout: Request timeout in seconds
Returns:
Response object or None if all retries fail
"""
retries = 0
while retries <= max_retries:
try:
response = requests.get(url, timeout=timeout)
response.raise_for_status() # Raise exception for 4XX/5XX status codes
return response
except Timeout:
wait_time = backoff_factor * (2 ** retries)
print(f"Request timed out. Retrying in {wait_time} seconds...")
except HTTPError as e:
# Don't retry for client errors (4XX) except 429 (too many requests)
if 400 <= e.response.status_code < 500 and e.response.status_code != 429:
print(f"Client error: {e}")
return e.response
wait_time = backoff_factor * (2 ** retries)
print(f"HTTP error: {e}. Retrying in {wait_time} seconds...")
except RequestException as e:
wait_time = backoff_factor * (2 ** retries)
print(f"Request failed: {e}. Retrying in {wait_time} seconds...")
retries += 1
if retries <= max_retries:
time.sleep(wait_time)
print(f"Failed after {max_retries} retries")
return None
# Usage
response = fetch_with_retry('https://api.example.com/data')
if response and response.status_code == 200:
data = response.json()
print("Data retrieved successfully:", data)
else:
print("Failed to retrieve data")
Handling Rate Limits
Many APIs impose rate limits to prevent abuse. It's important to respect these limits and implement proper handling.
import requests
import time
class RateLimitedAPI:
def __init__(self, base_url, requests_per_minute=60):
self.base_url = base_url
self.min_interval = 60.0 / requests_per_minute # Minimum seconds between requests
self.last_request_time = 0
def _wait_if_needed(self):
"""Wait to comply with rate limit"""
elapsed = time.time() - self.last_request_time
if elapsed < self.min_interval:
sleep_time = self.min_interval - elapsed
time.sleep(sleep_time)
def make_request(self, endpoint, method='get', **kwargs):
"""Make a rate-limited request to the API"""
self._wait_if_needed()
url = f"{self.base_url}/{endpoint}"
response = None
try:
if method.lower() == 'get':
response = requests.get(url, **kwargs)
elif method.lower() == 'post':
response = requests.post(url, **kwargs)
elif method.lower() == 'put':
response = requests.put(url, **kwargs)
elif method.lower() == 'delete':
response = requests.delete(url, **kwargs)
else:
raise ValueError(f"Unsupported HTTP method: {method}")
# Check for rate limit headers and adjust if needed
if 'X-RateLimit-Remaining' in response.headers:
remaining = int(response.headers['X-RateLimit-Remaining'])
if remaining <= 1:
print("Rate limit almost reached, slowing down requests")
self.min_interval *= 2 # Double the wait time
# Handle 429 (Too Many Requests) status
if response.status_code == 429 and 'Retry-After' in response.headers:
retry_after = int(response.headers['Retry-After'])
print(f"Rate limit exceeded. Waiting for {retry_after} seconds")
time.sleep(retry_after)
# Retry the request
return self.make_request(endpoint, method, **kwargs)
finally:
self.last_request_time = time.time()
return response
# Example usage
api = RateLimitedAPI('https://api.example.com', requests_per_minute=30)
# Make multiple requests
for i in range(10):
response = api.make_request(f'items/{i}')
print(f"Request {i}: Status {response.status_code}")
Best Practices for API Usage
- Store API keys and sensitive information in environment variables, not in code
- Implement proper error handling for failed requests
- Add exponential backoff for retries to avoid overwhelming the API
- Respect rate limits to prevent being blocked
- Use appropriate HTTP methods (GET, POST, PUT, DELETE) according to the operation
- Cache responses when appropriate to reduce API calls
- Implement proper authentication and keep tokens secure
- Validate input parameters before sending them to the API
Building Your Own API
As you become more comfortable with using APIs, you might want to create your own. Here's a simple example using Flask:
from flask import Flask, request, jsonify
app = Flask(__name__)
# In-memory database for demonstration
books = [
{"id": 1, "title": "Python Crash Course", "author": "Eric Matthes"},
{"id": 2, "title": "Automate the Boring Stuff with Python", "author": "Al Sweigart"}
]
# GET all books
@app.route('/api/books', methods=['GET'])
def get_books():
return jsonify(books)
# GET a specific book
@app.route('/api/books/', methods=['GET'])
def get_book(book_id):
book = next((book for book in books if book['id'] == book_id), None)
if book:
return jsonify(book)
return jsonify({"error": "Book not found"}), 404
# POST a new book
@app.route('/api/books', methods=['POST'])
def add_book():
if not request.json or 'title' not in request.json or 'author' not in request.json:
return jsonify({"error": "Title and author are required"}), 400
new_book = {
"id": max(book['id'] for book in books) + 1 if books else 1,
"title": request.json['title'],
"author": request.json['author']
}
books.append(new_book)
return jsonify(new_book), 201
# PUT (update) a book
@app.route('/api/books/', methods=['PUT'])
def update_book(book_id):
book = next((book for book in books if book['id'] == book_id), None)
if not book:
return jsonify({"error": "Book not found"}), 404
if not request.json:
return jsonify({"error": "No data provided"}), 400
book['title'] = request.json.get('title', book['title'])
book['author'] = request.json.get('author', book['author'])
return jsonify(book)
# DELETE a book
@app.route('/api/books/', methods=['DELETE'])
def delete_book(book_id):
global books
book = next((book for book in books if book['id'] == book_id), None)
if not book:
return jsonify({"error": "Book not found"}), 404
books = [b for b in books if b['id'] != book_id]
return jsonify({"message": f"Book {book_id} deleted"})
if __name__ == '__main__':
app.run(debug=True)
Memory Tip for API Concepts
Remember API interactions with the "CRUD" acronym, which maps to HTTP methods:
- Create = POST
- Read = GET
- Update = PUT/PATCH
- Delete = DELETE
And remember the core components of API requests with "HUMP":
- Headers (metadata, auth)
- URL (endpoint)
- Method (GET, POST, etc.)
- Parameters/Payload (data)
4. Intermediate Projects
Now that we've covered key Python concepts, let's apply this knowledge to real-world projects. These intermediate projects will help you consolidate your skills and build practical applications.
4.1 Language Detector using Tkinter and langdetect
This project combines GUI development with natural language processing to create an application that can detect the language of input text.
Key Concepts Used:
- Tkinter for GUI development
- Natural language processing with langdetect
- Event handling
- Exception handling
Project Implementation:
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
from langdetect import detect, LangDetectException
import pycountry
class LanguageDetectorApp:
def __init__(self, root):
self.root = root
self.root.title("Language Detector")
self.root.geometry("600x400")
self.root.configure(bg="#f0f4f8")
# Configure styles
self.style = ttk.Style()
self.style.configure("TButton", font=("Arial", 12), padding=10)
self.style.configure("TLabel", font=("Arial", 12), padding=5, background="#f0f4f8")
# Title label
self.title_label = ttk.Label(
root,
text="Language Detector",
font=("Arial", 18, "bold"),
background="#f0f4f8"
)
self.title_label.pack(pady=10)
# Instructions label
self.instructions = ttk.Label(
root,
text="Enter text below and click 'Detect Language'",
background="#f0f4f8"
)
self.instructions.pack(pady=5)
# Text input area
self.text_input = scrolledtext.ScrolledText(
root,
width=50,
height=10,
font=("Arial", 12),
wrap=tk.WORD
)
self.text_input.pack(pady=10, padx=20)
# Detect button
self.detect_button = ttk.Button(
root,
text="Detect Language",
command=self.detect_language
)
self.detect_button.pack(pady=10)
# Result frame
self.result_frame = ttk.Frame(root, padding="10")
self.result_frame.pack(fill="x", padx=20)
# Result label
self.result_label = ttk.Label(
self.result_frame,
text="Detected Language: ",
font=("Arial", 12, "bold"),
background="#f0f4f8"
)
self.result_label.pack(side="left")
# Language result
self.language_result = ttk.Label(
self.result_frame,
text="None",
font=("Arial", 12),
background="#f0f4f8"
)
self.language_result.pack(side="left")
# Language code result
self.code_result = ttk.Label(
self.result_frame,
text="",
font=("Arial", 12),
background="#f0f4f8"
)
self.code_result.pack(side="left", padx=5)
def detect_language(self):
"""Detect the language of the input text."""
text = self.text_input.get("1.0", tk.END).strip()
if not text:
messagebox.showwarning("Empty Input", "Please enter some text to detect the language.")
return
try:
# Detect language
lang_code = detect(text)
# Get full language name
language_name = "Unknown"
try:
language = pycountry.languages.get(alpha_2=lang_code)
if language:
language_name = language.name
except:
# Fallback for languages not in pycountry
language_map = {
'zh-cn': 'Chinese (Simplified)',
'zh-tw': 'Chinese (Traditional)',
'en': 'English',
'es': 'Spanish',
'fr': 'French',
'de': 'German',
'ja': 'Japanese',
'ko': 'Korean',
'ru': 'Russian',
'ar': 'Arabic'
}
language_name = language_map.get(lang_code, f"Unknown ({lang_code})")
# Update the result labels
self.language_result.config(text=language_name)
self.code_result.config(text=f"({lang_code})")
except LangDetectException as e:
messagebox.showerror("Detection Error", f"Error detecting language: {str(e)}")
except Exception as e:
messagebox.showerror("Error", f"An unexpected error occurred: {str(e)}")
if __name__ == "__main__":
root = tk.Tk()
app = LanguageDetectorApp(root)
root.mainloop()
Requirements:
Install the required packages:
pip install langdetect pycountry
How It Works:
- The user enters text in the input area
- When the "Detect Language" button is clicked, the application sends the text to the langdetect library
- The detected language code is converted to a full language name using pycountry
- The result is displayed in the UI
- Error handling ensures the application doesn't crash when invalid input is provided
Extensions and Improvements:
- Add confidence scores for multiple possible languages
- Implement translation functionality
- Add support for file input (detect language from text files)
- Create a history feature to save previous detections
4.2 Text to Speech Converter
This project creates a text-to-speech application that converts written text into spoken words, with options to adjust voice, speed, and save the audio output.
Key Concepts Used:
- Text-to-speech conversion with pyttsx3
- GUI development with Tkinter
- File handling for saving audio
- Multi-threading for non-blocking UI
Project Implementation:
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, filedialog
import pyttsx3
import threading
import os
class TextToSpeechApp:
def __init__(self, root):
self.root = root
self.root.title("Text to Speech Converter")
self.root.geometry("650x500")
self.root.configure(bg="#f5f5f5")
# Initialize text-to-speech engine
self.engine = pyttsx3.init()
self.voices = {}
self.populate_voices()
# Default rate and volume
self.rate = 150
self.volume = 1.0
# Create main frame
self.main_frame = ttk.Frame(root, padding="20")
self.main_frame.pack(fill="both", expand=True)
# Title
self.title_label = ttk.Label(
self.main_frame,
text="Text to Speech Converter",
font=("Arial", 18, "bold")
)
self.title_label.pack(pady=10)
# Text input area
self.text_label = ttk.Label(
self.main_frame,
text="Enter text to convert:",
font=("Arial", 12)
)
self.text_label.pack(anchor="w", pady=(10, 5))
self.text_input = scrolledtext.ScrolledText(
self.main_frame,
width=60,
height=10,
font=("Arial", 12),
wrap=tk.WORD
)
self.text_input.pack(pady=5, fill="both", expand=True)
# Controls frame
self.controls_frame = ttk.Frame(self.main_frame)
self.controls_frame.pack(fill="x", pady=10)
# Voice selection
self.voice_label = ttk.Label(
self.controls_frame,
text="Voice:",
font=("Arial", 12)
)
self.voice_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
self.voice_var = tk.StringVar()
self.voice_dropdown = ttk.Combobox(
self.controls_frame,
textvariable=self.voice_var,
values=list(self.voices.keys()),
width=20,
state="readonly"
)
self.voice_dropdown.grid(row=0, column=1, padx=5, pady=5, sticky="w")
if self.voices:
self.voice_dropdown.current(0)
self.voice_var.trace_add("write", self.on_voice_change)
# Rate control
self.rate_label = ttk.Label(
self.controls_frame,
text="Speed:",
font=("Arial", 12)
)
self.rate_label.grid(row=0, column=2, padx=5, pady=5, sticky="w")
self.rate_var = tk.IntVar(value=self.rate)
self.rate_slider = ttk.Scale(
self.controls_frame,
from_=50,
to=300,
orient="horizontal",
variable=self.rate_var,
length=100,
command=self.on_rate_change
)
self.rate_slider.grid(row=0, column=3, padx=5, pady=5)
self.rate_value_label = ttk.Label(
self.controls_frame,
text=str(self.rate),
width=3
)
self.rate_value_label.grid(row=0, column=4, padx=5, pady=5)
# Volume control
self.volume_label = ttk.Label(
self.controls_frame,
text="Volume:",
font=("Arial", 12)
)
self.volume_label.grid(row=1, column=0, padx=5, pady=5, sticky="w")
self.volume_var = tk.DoubleVar(value=self.volume)
self.volume_slider = ttk.Scale(
self.controls_frame,
from_=0.0,
to=1.0,
orient="horizontal",
variable=self.volume_var,
length=100,
command=self.on_volume_change
)
self.volume_slider.grid(row=1, column=1, padx=5, pady=5)
self.volume_value_label = ttk.Label(
self.controls_frame,
text=f"{int(self.volume * 100)}%",
width=4
)
self.volume_value_label.grid(row=1, column=2, padx=5, pady=5)
# Buttons frame
self.buttons_frame = ttk.Frame(self.main_frame)
self.buttons_frame.pack(fill="x", pady=10)
# Speak button
self.speak_button = ttk.Button(
self.buttons_frame,
text="Speak",
command=self.speak_text,
width=15
)
self.speak_button.grid(row=0, column=0, padx=5, pady=5)
# Stop button
self.stop_button = ttk.Button(
self.buttons_frame,
text="Stop",
command=self.stop_speaking,
width=15
)
self.stop_button.grid(row=0, column=1, padx=5, pady=5)
# Save as audio button
self.save_button = ttk.Button(
self.buttons_frame,
text="Save as Audio",
command=self.save_audio,
width=15
)
self.save_button.grid(row=0, column=2, padx=5, pady=5)
# Clear button
self.clear_button = ttk.Button(
self.buttons_frame,
text="Clear Text",
command=self.clear_text,
width=15
)
self.clear_button.grid(row=0, column=3, padx=5, pady=5)
# Status bar
self.status_var = tk.StringVar(value="Ready")
self.status_bar = ttk.Label(
root,
textvariable=self.status_var,
relief=tk.SUNKEN,
anchor=tk.W,
padding=(5, 2)
)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# Center the buttons
self.buttons_frame.grid_columnconfigure((0, 1, 2, 3), weight=1)
def populate_voices(self):
"""Populate the voices dictionary with available voices."""
for voice in self.engine.getProperty('voices'):
self.voices[f"{voice.name} ({voice.id})"] = voice.id
def on_voice_change(self, *args):
"""Handle voice change event."""
selected_voice = self.voice_var.get()
if selected_voice in self.voices:
self.engine.setProperty('voice', self.voices[selected_voice])
def on_rate_change(self, *args):
"""Handle rate change event."""
self.rate = self.rate_var.get()
self.rate_value_label.config(text=str(self.rate))
self.engine.setProperty('rate', self.rate)
def on_volume_change(self, *args):
"""Handle volume change event."""
self.volume = self.volume_var.get()
self.volume_value_label.config(text=f"{int(self.volume * 100)}%")
self.engine.setProperty('volume', self.volume)
def speak_text(self):
"""Speak the entered text."""
text = self.text_input.get("1.0", tk.END).strip()
if not text:
messagebox.showwarning("Empty Text", "Please enter some text to speak.")
return
# Update UI state
self.status_var.set("Speaking...")
self.speak_button.config(state="disabled")
# Start speaking in a separate thread to avoid blocking the UI
threading.Thread(target=self._speak_thread, args=(text,), daemon=True).start()
def _speak_thread(self, text):
"""Thread function for speaking text."""
try:
self.engine.say(text)
self.engine.runAndWait()
except Exception as e:
messagebox.showerror("Error", f"An error occurred: {str(e)}")
finally:
# Update UI state back to normal
self.root.after(0, self._reset_ui)
def _reset_ui(self):
"""Reset UI state after speaking."""
self.status_var.set("Ready")
self.speak_button.config(state="normal")
def stop_speaking(self):
"""Stop current speech."""
self.engine.stop()
self.status_var.set("Stopped")
def clear_text(self):
"""Clear the text input area."""
self.text_input.delete("1.0", tk.END)
def save_audio(self):
"""Save speech as an audio file."""
text = self.text_input.get("1.0", tk.END).strip()
if not text:
messagebox.showwarning("Empty Text", "Please enter some text to save as audio.")
return
filetypes = [("WAV files", "*.wav"), ("All files", "*.*")]
filename = filedialog.asksaveasfilename(
title="Save Audio File",
filetypes=filetypes,
defaultextension=".wav"
)
if not filename:
return # User canceled
self.status_var.set(f"Saving audio to {os.path.basename(filename)}...")
self.save_button.config(state="disabled")
# Start saving in a separate thread to avoid blocking the UI
threading.Thread(target=self._save_thread, args=(text, filename), daemon=True).start()
def _save_thread(self, text, filename):
"""Thread function for saving audio."""
try:
# Save speech to file (requires mpg123 for mp3 or similar)
# This example just generates the speech without saving
# For actual file saving, you'd need to use a library like gTTS
messagebox.showinfo("Information",
"The pyttsx3 library doesn't directly support saving to files.\n"
"To save audio files, consider using the gTTS library instead.")
# If you had gTTS, it would look like:
# from gtts import gTTS
# tts = gTTS(text=text, lang='en')
# tts.save(filename)
except Exception as e:
messagebox.showerror("Error", f"Failed to save audio: {str(e)}")
finally:
# Update UI state back to normal
self.root.after(0, self._reset_save_ui)
def _reset_save_ui(self):
"""Reset UI state after saving."""
self.status_var.set("Ready")
self.save_button.config(state="normal")
if __name__ == "__main__":
root = tk.Tk()
app = TextToSpeechApp(root)
root.mainloop()
Requirements:
Install the required packages:
pip install pyttsx3
Note
For fully functional audio file saving, you should use additional libraries like gTTS (Google Text-to-Speech). The example above shows where this would be implemented.
How It Works:
- The application initializes the pyttsx3 engine and retrieves available voices
- The user enters text and can adjust voice, speaking rate, and volume
- When the "Speak" button is clicked, the text is passed to the TTS engine in a separate thread
- Threading prevents the UI from freezing during speech
- The user can stop the speech at any time
Extensions and Improvements:
- Add support for multiple languages using different TTS engines
- Implement audio file saving with gTTS or another library
- Add text-to-speech highlighting to show current reading position
- Create a history feature to save frequently used phrases
- Implement a feature to read text from PDF or web pages
4.3 Alarm Clock
This project creates a feature-rich alarm clock application with a digital display, multiple alarms, snooze functionality, and customizable alarm sounds.
Key Concepts Used:
- Time handling in Python
- Multithreading for alarm monitoring
- Event-driven programming
- Audio playback
- Data persistence for saving alarms
Project Implementation:
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import time
import datetime
import threading
import json
import os
import pygame # For playing alarm sounds
class AlarmClock:
def __init__(self, root):
self.root = root
self.root.title("Alarm Clock")
self.root.geometry("500x600")
self.root.configure(bg="#f0f0f0")
# Initialize pygame mixer for audio
pygame.mixer.init()
# Default alarm sound
self.default_alarm_sound = "alarm.mp3" # You should include this file or find a default
# Alarm data structure
self.alarms = []
self.active_alarm = None
self.snooze_time = 5 # Default snooze time in minutes
# Create and configure styles
self.style = ttk.Style()
self.style.configure("TButton", font=("Arial", 12))
self.style.configure("TLabel", font=("Arial", 12), background="#f0f0f0")
# Main frame
self.main_frame = ttk.Frame(root, padding="20")
self.main_frame.pack(fill="both", expand=True)
# Clock display
self.clock_frame = ttk.Frame(self.main_frame)
self.clock_frame.pack(fill="x", pady=10)
self.time_display = ttk.Label(
self.clock_frame,
font=("Digital-7", 48, "bold"),
background="#000000",
foreground="#00ff00",
text="00:00:00",
anchor="center",
padding=10
)
self.time_display.pack(fill="x")
self.date_display = ttk.Label(
self.clock_frame,
font=("Arial", 14),
text="",
anchor="center"
)
self.date_display.pack(fill="x", pady=5)
# Alarm setup frame
self.setup_frame = ttk.LabelFrame(self.main_frame, text="Set Alarm", padding=10)
self.setup_frame.pack(fill="x", pady=10)
# Time selection
self.time_frame = ttk.Frame(self.setup_frame)
self.time_frame.pack(fill="x", pady=5)
# Hour selection
ttk.Label(self.time_frame, text="Hour:").grid(row=0, column=0, padx=5)
self.hour_var = tk.StringVar()
self.hour_combo = ttk.Combobox(
self.time_frame,
textvariable=self.hour_var,
values=[f"{i:02d}" for i in range(24)],
width=5,
state="readonly"
)
self.hour_combo.grid(row=0, column=1, padx=5)
self.hour_combo.current(datetime.datetime.now().hour)
# Minute selection
ttk.Label(self.time_frame, text="Minute:").grid(row=0, column=2, padx=5)
self.minute_var = tk.StringVar()
self.minute_combo = ttk.Combobox(
self.time_frame,
textvariable=self.minute_var,
values=[f"{i:02d}" for i in range(60)],
width=5,
state="readonly"
)
self.minute_combo.grid(row=0, column=3, padx=5)
self.minute_combo.current(datetime.datetime.now().minute)
# Alarm name
self.name_frame = ttk.Frame(self.setup_frame)
self.name_frame.pack(fill="x", pady=5)
ttk.Label(self.name_frame, text="Alarm Name:").pack(side="left", padx=5)
self.alarm_name_var = tk.StringVar(value="My Alarm")
self.alarm_name_entry = ttk.Entry(
self.name_frame,
textvariable=self.alarm_name_var,
width=25
)
self.alarm_name_entry.pack(side="left", padx=5, fill="x", expand=True)
# Alarm sound
self.sound_frame = ttk.Frame(self.setup_frame)
self.sound_frame.pack(fill="x", pady=5)
ttk.Label(self.sound_frame, text="Alarm Sound:").pack(side="left", padx=5)
self.alarm_sound_var = tk.StringVar(value=self.default_alarm_sound)
self.alarm_sound_entry = ttk.Entry(
self.sound_frame,
textvariable=self.alarm_sound_var,
width=25
)
self.alarm_sound_entry.pack(side="left", padx=5, fill="x", expand=True)
self.browse_button = ttk.Button(
self.sound_frame,
text="Browse",
command=self.browse_alarm_sound
)
self.browse_button.pack(side="left", padx=5)
# Days selection (for recurring alarms)
self.days_frame = ttk.Frame(self.setup_frame)
self.days_frame.pack(fill="x", pady=5)
ttk.Label(self.days_frame, text="Repeat on:").pack(side="left", padx=5)
self.days_vars = {}
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
for day in days:
self.days_vars[day] = tk.BooleanVar(value=False)
ttk.Checkbutton(
self.days_frame,
text=day,
variable=self.days_vars[day]
).pack(side="left")
# Set alarm button
self.set_button = ttk.Button(
self.setup_frame,
text="Set Alarm",
command=self.set_alarm
)
self.set_button.pack(pady=10)
# Alarms list frame
self.list_frame = ttk.LabelFrame(self.main_frame, text="Alarms", padding=10)
self.list_frame.pack(fill="both", expand=True, pady=10)
# Scrollable frame for alarms
self.canvas = tk.Canvas(self.list_frame)
self.scrollbar = ttk.Scrollbar(self.list_frame, orient="vertical", command=self.canvas.yview)
self.scrollable_frame = ttk.Frame(self.canvas)
self.scrollable_frame.bind(
"",
lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))
)
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.pack(side="left", fill="both", expand=True)
self.scrollbar.pack(side="right", fill="y")
# Status bar
self.status_var = tk.StringVar(value="Ready")
self.status_bar = ttk.Label(
root,
textvariable=self.status_var,
relief=tk.SUNKEN,
anchor=tk.W,
padding=(5, 2)
)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# Load saved alarms
self.load_alarms()
# Start updating the clock
self.update_clock()
# Start the alarm checker thread
self.alarm_thread = threading.Thread(target=self.check_alarms, daemon=True)
self.alarm_thread.start()
def update_clock(self):
"""Update the digital clock display."""
current_time = time.strftime("%H:%M:%S")
current_date = time.strftime("%A, %B %d, %Y")
self.time_display.config(text=current_time)
self.date_display.config(text=current_date)
# Update every second
self.root.after(1000, self.update_clock)
def browse_alarm_sound(self):
"""Open file dialog to select alarm sound."""
filetypes = [
("Audio files", "*.mp3 *.wav"),
("MP3 files", "*.mp3"),
("WAV files", "*.wav"),
("All files", "*.*")
]
filename = filedialog.askopenfilename(
title="Select Alarm Sound",
filetypes=filetypes
)
if filename:
self.alarm_sound_var.set(filename)
def set_alarm(self):
"""Add a new alarm to the list."""
try:
# Get alarm time
hour = int(self.hour_var.get())
minute = int(self.minute_var.get())
# Validate time
if not (0 <= hour < 24 and 0 <= minute < 60):
messagebox.showerror("Invalid Time", "Please enter a valid time.")
return
# Get other alarm properties
name = self.alarm_name_var.get()
sound = self.alarm_sound_var.get()
# Get selected days
days = [day for day, var in self.days_vars.items() if var.get()]
# Create alarm object
alarm = {
"id": time.time(), # Use timestamp as unique ID
"name": name,
"hour": hour,
"minute": minute,
"sound": sound,
"days": days,
"enabled": True
}
# Add to alarms list
self.alarms.append(alarm)
# Update the display
self.refresh_alarm_list()
# Save alarms
self.save_alarms()
# Show confirmation
self.status_var.set(f"Alarm '{name}' set for {hour:02d}:{minute:02d}")
# Reset form
self.alarm_name_var.set("My Alarm")
except ValueError:
messagebox.showerror("Invalid Input", "Please enter valid numbers for hour and minute.")
def refresh_alarm_list(self):
"""Update the display of alarms."""
# Clear existing alarms display
for widget in self.scrollable_frame.winfo_children():
widget.destroy()
# No alarms message
if not self.alarms:
ttk.Label(
self.scrollable_frame,
text="No alarms set. Use the form above to create an alarm.",
padding=10
).pack()
return
# Sort alarms by time
sorted_alarms = sorted(self.alarms, key=lambda a: (a["hour"], a["minute"]))
# Add each alarm to the display
for i, alarm in enumerate(sorted_alarms):
frame = ttk.Frame(self.scrollable_frame, padding=5)
frame.pack(fill="x", pady=2)
# Add a line if not the first alarm
if i > 0:
separator = ttk.Separator(self.scrollable_frame, orient="horizontal")
separator.pack(fill="x", pady=5)
# Enabled status checkbox
enabled_var = tk.BooleanVar(value=alarm["enabled"])
ttk.Checkbutton(
frame,
variable=enabled_var,
command=lambda a=alarm, v=enabled_var: self.toggle_alarm(a, v.get())
).pack(side="left")
# Time and name
days_text = f" [{', '.join(alarm['days'])}]" if alarm["days"] else ""
ttk.Label(
frame,
text=f"{alarm['hour']:02d}:{alarm['minute']:02d} - {alarm['name']}{days_text}",
font=("Arial", 12)
).pack(side="left", padx=5)
# Delete button
ttk.Button(
frame,
text="Delete",
command=lambda a=alarm: self.delete_alarm(a)
).pack(side="right")
def toggle_alarm(self, alarm, enabled):
"""Enable or disable an alarm."""
for a in self.alarms:
if a["id"] == alarm["id"]:
a["enabled"] = enabled
break
self.save_alarms()
def delete_alarm(self, alarm):
"""Remove an alarm from the list."""
self.alarms = [a for a in self.alarms if a["id"] != alarm["id"]]
self.refresh_alarm_list()
self.save_alarms()
def save_alarms(self):
"""Save alarms to a JSON file."""
try:
with open("alarms.json", "w") as f:
json.dump(self.alarms, f)
except Exception as e:
messagebox.showerror("Error", f"Failed to save alarms: {str(e)}")
def load_alarms(self):
"""Load alarms from a JSON file."""
try:
if os.path.exists("alarms.json"):
with open("alarms.json", "r") as f:
self.alarms = json.load(f)
self.refresh_alarm_list()
except Exception as e:
messagebox.showerror("Error", f"Failed to load alarms: {str(e)}")
def check_alarms(self):
"""Check if any alarms should trigger."""
while True:
if self.active_alarm:
# An alarm is already ringing
time.sleep(1)
continue
now = datetime.datetime.now()
current_time = (now.hour, now.minute)
current_day = now.strftime("%a")
for alarm in self.alarms:
if not alarm["enabled"]:
continue
alarm_time = (alarm["hour"], alarm["minute"])
# Check if this alarm should ring now
if current_time == alarm_time:
# For recurring alarms, check the day
if alarm["days"] and current_day not in alarm["days"]:
continue
# Trigger the alarm
self.trigger_alarm(alarm)
break
# Check every second
time.sleep(1)
def trigger_alarm(self, alarm):
"""Activate an alarm."""
self.active_alarm = alarm
self.root.after(0, self._show_alarm_dialog, alarm)
# Play the sound
try:
sound_file = alarm["sound"]
if not os.path.exists(sound_file):
sound_file = self.default_alarm_sound
pygame.mixer.music.load(sound_file)
pygame.mixer.music.play(-1) # Loop indefinitely
except Exception as e:
print(f"Error playing alarm sound: {e}")
def _show_alarm_dialog(self, alarm):
"""Show the alarm dialog on the main thread."""
dialog = tk.Toplevel(self.root)
dialog.title("Alarm!")
dialog.geometry("400x200")
dialog.attributes('-topmost', True)
# Make it stand out
dialog.configure(bg="#ff9999")
# Alarm message
tk.Label(
dialog,
text=f"Time's up!",
font=("Arial", 18, "bold"),
bg="#ff9999"
).pack(pady=10)
tk.Label(
dialog,
text=alarm["name"],
font=("Arial", 14),
bg="#ff9999"
).pack(pady=5)
# Buttons frame
buttons_frame = tk.Frame(dialog, bg="#ff9999")
buttons_frame.pack(pady=20)
# Dismiss button
tk.Button(
buttons_frame,
text="Dismiss",
font=("Arial", 12),
command=lambda: self.stop_alarm(dialog)
).pack(side="left", padx=10)
# Snooze button
tk.Button(
buttons_frame,
text=f"Snooze ({self.snooze_time} min)",
font=("Arial", 12),
command=lambda: self.snooze_alarm(dialog)
).pack(side="left", padx=10)
def stop_alarm(self, dialog):
"""Stop the alarm sound and close the dialog."""
pygame.mixer.music.stop()
dialog.destroy()
self.active_alarm = None
def snooze_alarm(self, dialog):
"""Snooze the alarm for a few minutes."""
pygame.mixer.music.stop()
dialog.destroy()
# Create a new temporary alarm for the snooze time
now = datetime.datetime.now()
snooze_time = now + datetime.timedelta(minutes=self.snooze_time)
snooze_alarm = dict(self.active_alarm) # Copy the active alarm
snooze_alarm["hour"] = snooze_time.hour
snooze_alarm["minute"] = snooze_time.minute
snooze_alarm["days"] = [] # Don't repeat
snooze_alarm["name"] = f"{self.active_alarm['name']} (Snoozed)"
# Add the snoozed alarm
self.alarms.append(snooze_alarm)
self.refresh_alarm_list()
self.save_alarms()
self.active_alarm = None
self.status_var.set(f"Alarm snoozed for {self.snooze_time} minutes")
if __name__ == "__main__":
root = tk.Tk()
app = AlarmClock(root)
root.mainloop()
Requirements:
Install the required packages:
pip install pygame
You'll also need an alarm sound file (MP3 or WAV) named "alarm.mp3" in the same directory as the script, or specify a different file path.
How It Works:
- The application displays a digital clock with the current time and date
- Users can create alarms by specifying time, name, sound, and recurring days
- A background thread continuously checks if any alarms should be triggered
- When an alarm is triggered, a dialog appears and the sound plays
- Users can dismiss the alarm or snooze it
- Alarms are saved to a JSON file for persistence
Extensions and Improvements:
- Add a custom snooze time option
- Implement different alarm types (countdown, timer, stopwatch)
- Add a feature to gradually increase alarm volume
- Create a system tray icon for background operation
- Add themes and color schemes for the clock display
- Implement alarm import/export functionality
4.4 QR Code Generator
This project creates a QR code generator application that converts text, URLs, contact information, and other data into QR codes with customizable settings.
Key Concepts Used:
- Working with external libraries (qrcode)
- Image generation and manipulation
- GUI development with Tkinter
- File handling for saving QR codes
- Data formatting for different QR code types
Project Implementation:
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import qrcode
from PIL import Image, ImageTk
import os
import io
class QRCodeGenerator:
def __init__(self, root):
self.root = root
self.root.title("QR Code Generator")
self.root.geometry("800x600")
self.root.configure(bg="#f5f5f5")
# QR Code parameters
self.qr_version = 1 # QR code version (1-40)
self.qr_box_size = 10 # Size of each box in the QR code
self.qr_border = 4 # Border size
self.qr_color = "black" # Foreground color
self.qr_bg_color = "white" # Background color
# Current QR image
self.current_qr_image = None
# Create main frame
self.main_frame = ttk.Frame(root, padding="20")
self.main_frame.pack(fill="both", expand=True)
# Title
self.title_label = ttk.Label(
self.main_frame,
text="QR Code Generator",
font=("Arial", 18, "bold")
)
self.title_label.pack(pady=10)
# Split into left and right frames
self.content_frame = ttk.Frame(self.main_frame)
self.content_frame.pack(fill="both", expand=True)
# Left frame for input options
self.left_frame = ttk.Frame(self.content_frame, padding=10)
self.left_frame.pack(side="left", fill="both", expand=True)
# Right frame for QR code display
self.right_frame = ttk.Frame(self.content_frame, padding=10)
self.right_frame.pack(side="right", fill="both", expand=True)
# QR Code type selection
self.type_frame = ttk.LabelFrame(self.left_frame, text="QR Code Type", padding=10)
self.type_frame.pack(fill="x", pady=5)
self.qr_type_var = tk.StringVar(value="URL")
qr_types = ["URL", "Text", "Email", "Phone", "SMS", "WiFi", "vCard"]
for i, qr_type in enumerate(qr_types):
ttk.Radiobutton(
self.type_frame,
text=qr_type,
variable=self.qr_type_var,
value=qr_type,
command=self.update_input_fields
).grid(row=i//3, column=i%3, sticky="w", padx=10, pady=5)
# Input fields frame
self.input_frame = ttk.LabelFrame(self.left_frame, text="QR Code Content", padding=10)
self.input_frame.pack(fill="both", expand=True, pady=5)
# Initial fields (will be updated based on QR type)
self.input_fields = {}
# Settings frame
self.settings_frame = ttk.LabelFrame(self.left_frame, text="QR Code Settings", padding=10)
self.settings_frame.pack(fill="x", pady=5)
# Version setting
ttk.Label(self.settings_frame, text="Version:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
self.version_var = tk.IntVar(value=self.qr_version)
version_spinner = ttk.Spinbox(
self.settings_frame,
from_=1,
to=40,
textvariable=self.version_var,
width=5,
command=self.update_settings
)
version_spinner.grid(row=0, column=1, sticky="w", padx=5, pady=5)
# Box size setting
ttk.Label(self.settings_frame, text="Box Size:").grid(row=0, column=2, sticky="w", padx=5, pady=5)
self.box_size_var = tk.IntVar(value=self.qr_box_size)
box_size_spinner = ttk.Spinbox(
self.settings_frame,
from_=1,
to=50,
textvariable=self.box_size_var,
width=5,
command=self.update_settings
)
box_size_spinner.grid(row=0, column=3, sticky="w", padx=5, pady=5)
# Border setting
ttk.Label(self.settings_frame, text="Border:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
self.border_var = tk.IntVar(value=self.qr_border)
border_spinner = ttk.Spinbox(
self.settings_frame,
from_=0,
to=10,
textvariable=self.border_var,
width=5,
command=self.update_settings
)
border_spinner.grid(row=1, column=1, sticky="w", padx=5, pady=5)
# Color settings
ttk.Label(self.settings_frame, text="Colors:").grid(row=1, column=2, sticky="w", padx=5, pady=5)
self.color_button = ttk.Button(
self.settings_frame,
text="Choose Colors",
command=self.choose_colors
)
self.color_button.grid(row=1, column=3, sticky="w", padx=5, pady=5)
# Generate button
self.generate_button = ttk.Button(
self.left_frame,
text="Generate QR Code",
command=self.generate_qr_code
)
self.generate_button.pack(fill="x", pady=10)
# QR code display
self.qr_frame = ttk.LabelFrame(self.right_frame, text="Generated QR Code", padding=10)
self.qr_frame.pack(fill="both", expand=True)
self.qr_display = ttk.Label(self.qr_frame)
self.qr_display.pack(fill="both", expand=True, padx=10, pady=10)
# Save button
self.save_button = ttk.Button(
self.right_frame,
text="Save QR Code",
command=self.save_qr_code,
state="disabled"
)
self.save_button.pack(fill="x", pady=10)
# Status bar
self.status_var = tk.StringVar(value="Ready")
self.status_bar = ttk.Label(
root,
textvariable=self.status_var,
relief=tk.SUNKEN,
anchor=tk.W,
padding=(5, 2)
)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# Initialize input fields
self.update_input_fields()
def update_input_fields(self):
"""Update input fields based on selected QR code type."""
# Clear existing fields
for widget in self.input_frame.winfo_children():
widget.destroy()
self.input_fields = {}
qr_type = self.qr_type_var.get()
if qr_type == "URL":
ttk.Label(self.input_frame, text="URL:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
self.input_fields["url"] = ttk.Entry(self.input_frame, width=40)
self.input_fields["url"].grid(row=0, column=1, sticky="ew", padx=5, pady=5)
self.input_fields["url"].insert(0, "https://")
elif qr_type == "Text":
ttk.Label(self.input_frame, text="Text:").grid(row=0, column=0, sticky="nw", padx=5, pady=5)
self.input_fields["text"] = tk.Text(self.input_frame, width=40, height=5)
self.input_fields["text"].grid(row=0, column=1, sticky="ew", padx=5, pady=5)
elif qr_type == "Email":
ttk.Label(self.input_frame, text="To:").grid(row=
0 Comments