|
25 | 25 | import subprocess |
26 | 26 | import sys |
27 | 27 | import webbrowser |
| 28 | +import requests |
| 29 | +import babel |
28 | 30 |
|
29 | 31 | MIN_PYTHON = (3, 9) |
30 | 32 | if sys.version_info < MIN_PYTHON: |
@@ -475,7 +477,11 @@ def on_save_click(self): |
475 | 477 |
|
476 | 478 | def on_saverun_click(self): |
477 | 479 | self.save_config_values() |
478 | | - subprocess.Popen(f'"{MAIN_DIRECTORY}{glob.glob("main.*", root_dir=MAIN_DIRECTORY)[0]}"', shell=True) |
| 480 | + kwargs = {} |
| 481 | + if hasattr(subprocess, "DETACHED_PROCESS"): |
| 482 | + kwargs["creationflags"] = subprocess.DETACHED_PROCESS |
| 483 | + subprocess.Popen([sys.executable, os.path.join(os.getcwd(), "main.py")], **kwargs) |
| 484 | +# subprocess.Popen(f'"{MAIN_DIRECTORY}{glob.glob("main.*", root_dir=MAIN_DIRECTORY)[0]}"', shell=True) |
479 | 485 | self.window.destroy() |
480 | 486 |
|
481 | 487 | def on_brightness_change(self, e=None): |
@@ -554,7 +560,7 @@ def __init__(self, main_window: TuringConfigWindow): |
554 | 560 | self.window = Toplevel() |
555 | 561 | self.window.withdraw() |
556 | 562 | self.window.title('Configure weather & ping') |
557 | | - self.window.geometry("750x400") |
| 563 | + self.window.geometry("750x680") |
558 | 564 |
|
559 | 565 | self.main_window = main_window |
560 | 566 |
|
@@ -614,11 +620,39 @@ def __init__(self, main_window: TuringConfigWindow): |
614 | 620 | self.lang_cb = ttk.Combobox(self.window, values=list(weather_lang_map.values()), state='readonly') |
615 | 621 | self.lang_cb.place(x=190, y=325, width=250) |
616 | 622 |
|
| 623 | + self.citysearch1_label = ttk.Label(self.window, text='Location search', font='bold') |
| 624 | + self.citysearch1_label.place(x=80, y=370) |
| 625 | + |
| 626 | + self.citysearch2_label = ttk.Label(self.window, text="Enter location to automatically get coordinates (latitude/longitude).\n" |
| 627 | + "For example \"Berlin\" \"London, GB\", \"London, Quebec\".\n" |
| 628 | + "Remember to set valid API key and pick language first!") |
| 629 | + self.citysearch2_label.place(x=10, y=396) |
| 630 | + |
| 631 | + self.citysearch3_label = ttk.Label(self.window, text="Enter location") |
| 632 | + self.citysearch3_label.place(x=10, y=474) |
| 633 | + self.citysearch_entry = ttk.Entry(self.window) |
| 634 | + self.citysearch_entry.place(x=140, y=470, width=300) |
| 635 | + self.citysearch_btn = ttk.Button(self.window, text="Search", command=lambda: self.on_search_click()) |
| 636 | + self.citysearch_btn.place(x=450, y=468, height=40, width=130) |
| 637 | + |
| 638 | + self.citysearch4_label = ttk.Label(self.window, text="Select location\n(use after Search)") |
| 639 | + self.citysearch4_label.place(x=10, y=540) |
| 640 | + self.citysearch_cb = ttk.Combobox(self.window, values=[], state='readonly') |
| 641 | + self.citysearch_cb.place(x=140, y=544, width=360) |
| 642 | + self.citysearch_btn2 = ttk.Button(self.window, text="Fill in lat/long", command=lambda: self.on_filllatlong_click()) |
| 643 | + self.citysearch_btn2.place(x=520, y=540, height=40, width=130) |
| 644 | + |
| 645 | + self.citysearch_warn_label = ttk.Label(self.window, text="") |
| 646 | + self.citysearch_warn_label.place(x=20, y=600) |
| 647 | + self.citysearch_warn_label.config(foreground="#ff0000") |
| 648 | + |
617 | 649 | self.save_btn = ttk.Button(self.window, text="Save settings", command=lambda: self.on_save_click()) |
618 | | - self.save_btn.place(x=590, y=340, height=50, width=130) |
| 650 | + self.save_btn.place(x=590, y=620, height=50, width=130) |
619 | 651 |
|
620 | 652 | self.window.protocol("WM_DELETE_WINDOW", self.on_closing) |
621 | 653 |
|
| 654 | + self._city_entries = [] |
| 655 | + |
622 | 656 | def validateCoord(self, coord: str): |
623 | 657 | if not coord: |
624 | 658 | return True |
@@ -666,6 +700,64 @@ def load_config_values(self, config): |
666 | 700 | self.lang_cb.set(weather_lang_map[self.config['config']['WEATHER_LANGUAGE']]) |
667 | 701 | except: |
668 | 702 | self.lang_cb.set(weather_lang_map["en"]) |
| 703 | + |
| 704 | + def citysearch_show_warning(self, warning): |
| 705 | + self.citysearch_warn_label.config(text=warning) |
| 706 | + |
| 707 | + def on_search_click(self): |
| 708 | + OPENWEATHER_GEOAPI_URL = "http://api.openweathermap.org/geo/1.0/direct" |
| 709 | + api_key = self.api_entry.get() |
| 710 | + lang = [k for k, v in weather_lang_map.items() if v == self.lang_cb.get()][0] |
| 711 | + city = self.citysearch_entry.get() |
| 712 | + |
| 713 | + if len(api_key) == 0 or len(city) == 0: |
| 714 | + self.citysearch_show_warning("API key and city name cannot be empty.") |
| 715 | + return |
| 716 | + |
| 717 | + try: |
| 718 | + request = requests.get(OPENWEATHER_GEOAPI_URL, timeout=5, params={"appid": api_key, "lang": lang, |
| 719 | + "q": city, "limit": 10}) |
| 720 | + except: |
| 721 | + self.citysearch_show_warning("Error fetching OpenWeatherMap Geo API") |
| 722 | + return |
| 723 | + |
| 724 | + if request.status_code == 401: |
| 725 | + self.citysearch_show_warning("Invalid OpenWeatherMap API key.") |
| 726 | + return |
| 727 | + elif request.status_code != 200: |
| 728 | + self.citysearch_show_warning(f"Error #{request.status_code} fetching OpenWeatherMap Geo API.") |
| 729 | + return |
| 730 | + |
| 731 | + self._city_entries = [] |
| 732 | + cb_entries = [] |
| 733 | + for entry in request.json(): |
| 734 | + name = entry['name'] |
| 735 | + state = entry['state'] |
| 736 | + lat = entry['lat'] |
| 737 | + long = entry['lon'] |
| 738 | + country_code = entry['country'].upper() |
| 739 | + country = babel.Locale(lang).territories[country_code] |
| 740 | + full_name = f"{name}, {state}, {country}" |
| 741 | + self._city_entries.append({"full_name": full_name, "lat": str(lat), "long": str(long)}) |
| 742 | + cb_entries.append(full_name) |
| 743 | + |
| 744 | + self.citysearch_cb.config(values = cb_entries) |
| 745 | + if len(cb_entries) == 0: |
| 746 | + self.citysearch_show_warning("No given city found.") |
| 747 | + else: |
| 748 | + self.citysearch_cb.current(0) |
| 749 | + self.citysearch_show_warning("Select your city now from list and apply \"Fill in lat/long\".") |
| 750 | + |
| 751 | + def on_filllatlong_click(self): |
| 752 | + if len(self._city_entries) == 0: |
| 753 | + self.citysearch_show_warning("No city selected or no search results.") |
| 754 | + return |
| 755 | + city = [i for i in self._city_entries if i['full_name'] == self.citysearch_cb.get()][0] |
| 756 | + self.lat_entry.delete(0, END) |
| 757 | + self.lat_entry.insert(0, city['lat']) |
| 758 | + self.long_entry.delete(0, END) |
| 759 | + self.long_entry.insert(0, city['long']) |
| 760 | + self.citysearch_show_warning(f"Lat/long values filled for {city['full_name']}") |
669 | 761 |
|
670 | 762 | def on_save_click(self): |
671 | 763 | self.save_config_values() |
|
0 commit comments