Skip to content

Commit

Permalink
Basic work
Browse files Browse the repository at this point in the history
Need to still utilize the progressbar, update docs, and clean code more but this is a good start. Please: DO NOT TOUCH. @im-coder-lg, please comment that you've read this.
  • Loading branch information
Moosems committed Aug 14, 2023
1 parent 2203e93 commit 5ecb774
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 79 deletions.
193 changes: 114 additions & 79 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
from __future__ import annotations

from platform import system
from tkinter import Menu, Tk, messagebox
from tkinter.ttk import Button, Entry, Frame, Label
from tkinter import Event, Menu, Tk, Toplevel, messagebox
from tkinter.ttk import Button, Entry, Frame, Label, Progressbar, Style

from pyowm import OWM
from pyowm.commons.exceptions import APIRequestError
from pyowm.commons.exceptions import NotFoundError as OWMNotFoundError
from requests import Response
from requests import get as requests_get


class ProgressBar(Toplevel):
def __init__(self, parent: App, *args, **kwargs):
# Set up window
super().__init__(parent, *args, **kwargs)
self.withdraw()
self.title("Loading...")
self.resizable(False, False)

# Set up widgets
self.main_frame = Frame(self)
self.main_frame.pack()

self.progressbar = Progressbar(
self.main_frame, orient="horizontal", length=200, mode="indeterminate"
)
self.progressbar.pack(padx=10, pady=10)

self.deiconify()


class App(Tk):
def __init__(self):
super().__init__()
Expand All @@ -23,69 +45,70 @@ def __init__(self):
self.menubar = Menu(self)
self.app_menu = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="App", menu=self.app_menu)
self.menubar.add_command(label="About Weather", command=self.about)
self.app_menu.add_command(label="About Weather", command=self.about)
self.config(menu=self.menubar)

# Set up style
Style().theme_use("clam")

# Set up window
self.title("Weather")
self.resizable(False, False)
self.configure(bg="white")

# Set up widgets
self.main_frame = Frame(self, padding=10)
self.main_frame = Frame(self)
self.main_frame.pack()

heading = Label(self.main_frame, text="Weather", font="Helvetica 13")
heading = Label(self.main_frame, text="Weather", font="Helvetica 25 bold")
heading.grid(row=0, column=0, columnspan=2, padx=10, pady=10)

self.cityname = Label(self.main_frame, text="", font=("Helvetica 13"))
self.cityname = Label(self.main_frame, text="City: None", font=("Helvetica 15"))
self.cityname.grid(row=1, column=0, columnspan=2)

self.searchbar = Entry(self.main_frame, width=42)
self.searchbar.grid(row=2, column=0, columnspan=2, padx=10, pady=10)
self.bind("<Return>", self.OWMCITY)

self.info_frame = Frame(self.main_frame, relief="sunken")
self.info_frame.grid(row=3, column=0, columnspan=2, padx=10, pady=10)

self.label_status = Label(
self.main_frame, text="", font=("Helvetica 13"))
self.label_status.grid(row=3, column=0, columnspan=2)
self.label_weather = Label(self.info_frame, text="", font=("Helvetica 13"))
self.label_weather.grid(row=0, column=0, columnspan=2)

self.label_temp = Label(
self.main_frame, text="", font=("Helvetica 13"))
self.label_temp.grid(row=4, column=0, columnspan=2)
self.label_temp = Label(self.info_frame, text="", font=("Helvetica 13"))
self.label_temp.grid(row=1, column=0, columnspan=2)

self.label_temp_max = Label(
self.main_frame, text="", font=("Helvetica 13"))
self.label_temp_max.grid(row=5, column=0, columnspan=2)
self.label_temp_max = Label(self.info_frame, text="", font=("Helvetica 13"))
self.label_temp_max.grid(row=2, column=0, columnspan=2)

self.label_temp_min = Label(
self.main_frame, text="", font=("Helvetica 13"))
self.label_temp_min.grid(row=6, column=0, columnspan=2)
self.label_temp_min = Label(self.info_frame, text="", font=("Helvetica 13"))
self.label_temp_min.grid(row=3, column=0, columnspan=2)

self.label_feels_like = Label(
self.main_frame, text="", font=("Helvetica 13"))
self.label_feels_like.grid(row=7, column=0, columnspan=2)
self.label_feels_like = Label(self.info_frame, text="", font=("Helvetica 13"))
self.label_feels_like.grid(row=4, column=0, columnspan=2)

self.label_humidity = Label(
self.main_frame, text="", font=("Helvetica 13"))
self.label_humidity.grid(row=8, column=0, columnspan=2)
self.label_humidity = Label(self.info_frame, text="", font=("Helvetica 13"))
self.label_humidity.grid(row=5, column=0, columnspan=2)

self.label_pressure = Label(
self.main_frame, text="", font=("Helvetica 13"))
self.label_pressure.grid(row=9, column=0, columnspan=2)
self.label_pressure = Label(self.info_frame, text="", font=("Helvetica 13"))
self.label_pressure.grid(row=6, column=0, columnspan=2)

self.label_visibility = Label(
self.main_frame, text="", font=("Helvetica 13"))
self.label_visibility.grid(row=10, column=0, columnspan=2)
self.label_visibility = Label(self.info_frame, text="", font=("Helvetica 13"))
self.label_visibility.grid(row=7, column=0, columnspan=2)

self.label_windspeed = Label(
self.main_frame, text="", font=("Helvetica 13"))
self.label_windspeed.grid(row=11, column=0, columnspan=2)
self.label_windspeed = Label(self.info_frame, text="", font=("Helvetica 13"))
self.label_windspeed.grid(row=8, column=0, columnspan=2)

# Set up buttons
Button(self.main_frame, text="Search for City", command=self.OWMCITY).grid(
row=12, column=0, padx=10, pady=10
)
Button(self.main_frame, text="Exit", command=self.exit_app).grid(
row=12, column=1, padx=10, pady=10
)

# Resize and deiconify
self.resize_app()
self.deiconify()

Expand All @@ -98,7 +121,7 @@ def about(self) -> App:
)
return self

def resize_app(self) -> App:
def resize_app(self, keep_placement: bool = False) -> App:
"""Use tkinter to detect the minimum size of the app, get the center of the screen, and place the app there."""
# Update widgets so minimum size is accurate
self.update_idletasks()
Expand All @@ -109,79 +132,91 @@ def resize_app(self) -> App:

# Get center of screen based on minimum size
x_coords = int(self.winfo_screenwidth() / 2 - minimum_width / 2)
y_coords = int(self.winfo_screenheight() / 2 - minimum_height / 2) - 20
# `-20` should deal with Dock on macOS and looks good on other OS's
y_coords = int(self.wm_maxsize()[1] / 2 - minimum_height / 2)

# Place app and make the minimum size the actual minimum size (non-infringable)
self.geometry(
f"{minimum_width}x{minimum_height}+{x_coords}+{y_coords}")
if keep_placement:
self.geometry(f"{minimum_width}x{minimum_height}")
else:
self.geometry(f"{minimum_width}x{minimum_height}+{x_coords}+{y_coords}")
self.wm_minsize(minimum_width, minimum_height)
return self

def exit_app(self) -> None:
"""Exit the app."""
self.destroy()

def OWMCITY(self) -> App:
def OWMCITY(self, _=Event | None) -> None:
"""Get the weather for a given city using the OpenWeatherMap API and display it in a label."""

# Get API key
api_key: str = "c439e1209216cc7e7c73a3a8d1d12bfd"
owm = OWM(api_key)
mgr = owm.weather_manager()
# Get city name
city: str = self.searchbar.get()
observation = mgr.weather_at_place(city)

# Check if city is empty
if not city:
self.cityname.configure(text="City: Needs Name")
return

# Check if city exists
try:
observation = mgr.weather_at_place(city)
except OWMNotFoundError or APIRequestError:
self.cityname.configure(text="City: Not Found")
self.update_labels()
return

# Get weather data
weather = observation.weather

# Send request to OpenWeatherMap API
response: Response = requests_get(
f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}"
)

# Check if request was successful
if response.status_code != 200:
self.label_status.configure(text="City not found")
self.cityname.configure(text="City: Connection Error")
self.update_labels()
return

# Get response data, simplify and create variables for usage
# TODO: Give all data in dual columns
# | Weather: Clear | Temp: 20°C |
# TODO: Allow users to change between imperial and metric (Fahrenheit and Celsius)
data = response.json()
main = data["main"]
temperature = weather.temperature("celsius")
wind = weather.wind(unit="meters_sec")
# Get temperature in Celsius
# temperature_kelvin: float = main["temp"]
# temperature_celsius = temperature_kelvin - 273.15
temp = temperature.get("temp", None)
temp_max = temperature.get("temp_max", None)
temp_min = temperature.get("temp_min", None)
feels_like = temperature.get("feels_like", None)
status: str = weather.status
detailed_status: str = weather.detailed_status
# Other data needed from the API
humidity = main["humidity"]
pressure = main["pressure"]
visibility = weather.visibility(unit="kilometers")
windspeed: float = wind["speed"]
# Put in label
self.cityname.configure(
text=data["name"] + ", " + data["sys"]["country"])
self.label_status.configure(
text="Weather: " + status + ":- " + detailed_status)
self.label_temp.configure(text="Temperature: " + f"{temp:.2f}°C")
self.label_temp_max.configure(
text="Maximum Temperature: " + f"{temp_max:.2f}°C"
)
self.label_temp_min.configure(
text="Minimum Temperature: " + f"{temp_min:.2f}°C"
)
self.label_feels_like.configure(
text="Feels like " + f"{feels_like:.2f}°C")
self.label_humidity.configure(text="Humidity: " + f"{humidity:.2f}%")
self.label_pressure.configure(
text="Pressure: " + f"{pressure:.2f}" + " hPa")
self.label_visibility.configure(
text="Visibility: " + f"{visibility:.2f}" + " km"
)
self.label_windspeed.configure(
text="Wind Speed: " + f"{windspeed:.2f}" + " meters per second"

# Update labels
self.update_labels(
f"Weather: {weather.status} ~ {weather.detailed_status}",
f"Current Temperature: {temperature.get('temp', None):.2f}°C",
f"Maximum Temperature: {temperature.get('temp_max', None):.2f}°C",
f"Minimum Temperature: {temperature.get('temp_min', None):.2f}°C",
f"Feels like {temperature.get('feels_like', None):.2f}°C",
f"Humidity: {main['humidity']:.2f}%",
f"Pressure: {main['pressure']:.2f} hPa",
f"Visibility: {weather.visibility(unit='kilometers'):.2f} km",
f"Wind Speed: { weather.wind(unit='meters_sec')['speed']:.2f} meters per second",
)
return self

def update_labels(self, data: list[str] = ["" for _ in range(9)]) -> None:
"""Clear all weather labels."""

self.label_weather.configure(text=data[0])
self.label_temp.configure(text=data[1])
self.label_temp_max.configure(text=data[2])
self.label_temp_min.configure(text=data[3])
self.label_feels_like.configure(text=data[4])
self.label_humidity.configure(text=data[5])
self.label_pressure.configure(text=data[6])
self.label_visibility.configure(text=data[7])
self.label_windspeed.configure(text=data[8])
return None


if __name__ == "__main__":
Expand Down
29 changes: 29 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import tkinter as tk


class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)

self.frame = tk.Frame(self)
self.frame.pack(side="top", fill="both", expand=True)

self.label = tk.Label(self, text="Hello, world")
self.label.pack(in_=self.frame)

button1 = tk.Button(self, text="Click to hide label", command=self.hide_label)
button1.pack()

button2 = tk.Button(self, text="Click to show label", command=self.show_label)
button2.pack()

def show_label(self, *_):
self.label.lift(self.frame)

def hide_label(self, *_):
self.label.lower(self.frame)


if __name__ == "__main__":
app = SampleApp()
app.mainloop()

0 comments on commit 5ecb774

Please sign in to comment.