From dedbe80815bcbd9e207172011ab8b92cf95ea70a Mon Sep 17 00:00:00 2001 From: Himadri Sekhar Basu Date: Mon, 12 Feb 2024 19:47:13 +0530 Subject: [PATCH] Refactor code and add scrollbar - Add scrollbar to main window to accomodate low res dispalys; fixes #52 - Load css from css file instead of hard-coding it as python. Provides better readability in IDEs - Remove eol spaces - Fix inconsistent tab spacing - Remove excutable permission from mousam.in, meson makes it excutable during install for all packaging methods --- src/application.css | 184 ------------- src/constants.py | 2 - src/css.py | 263 ------------------ src/css/style.css | 272 +++++++++++++++++++ src/frontendCardDayNight.py | 4 +- src/frontendForecast.py | 12 +- src/frontendUiDrawBar.py | 2 +- src/frontendUiDrawDayNight.py | 7 - src/frontendUiDrawbarLine.py | 8 +- src/main.py | 14 +- src/meson.build | 4 +- src/mousam.in | 0 src/mousam.py | 41 ++- src/windowAbout.py | 32 +-- src/windowLocations.py | 498 +++++++++++++++++----------------- src/windowPreferences.py | 209 +++++++------- 16 files changed, 681 insertions(+), 871 deletions(-) delete mode 100644 src/application.css delete mode 100644 src/css.py mode change 100755 => 100644 src/mousam.in diff --git a/src/application.css b/src/application.css deleted file mode 100644 index 16ffdf4..0000000 --- a/src/application.css +++ /dev/null @@ -1,184 +0,0 @@ -.condition_label { - font-size: 30px; -} -.main_temp_label { - font-size: 4rem; -} - -.btn_sm{ - font-size:.95rem; - padding-top: .2rem; - padding-bottom: .2rem; -} - - -.text-l1{ - font-size: 4rem; -} -.text-l2{ - font-size: 3.5rem; -} -.text-l3{ - font-size: 3rem; -} -.text-l4{ - font-size: 2.5rem; -} -.text-1{ - font-size: 2rem; -} -.text-2a{ - font-size: 1.8rem; -} -.text-2b{ - font-size: 1.5rem; -} -.text-3{ - font-size: 1.3rem; -} -.text-4{ - font-size: 1.1rem; -} -.text-5{ - font-size: 1.05rem; -} -.text-6{ - font-size: .95rem; -} -.text-7{ - font-size: 9rem; -} -.text-8{ - font-size: .85rem; -} -.text-9{ - font-size: .8rem; -} - -.title-xl{ - font-size:76px; -} -.title-l{ - font-size:42px; -} -.title-m{ - font-size:22px; -} -.title-s{ - font-size:18px; -} - -.light-1{ - opacity: .95; -} -.light-2{ - opacity: .9; -} -.light-3{ - opacity: .8; -} -.light-4{ - opacity: .75; -} -.light-5{ - opacity: .7; -} -.light-6{ - opacity: .6; -} - - -.bold-1{ - font-weight: 700; -} -.bold-2{ - font-weight: 600; -} -.bold-3{ - font-weight: 500; -} - - -.dark{ - color:#141414; -} - -.main_window{ - border-radius:13px; - border : 1px solid rgba(100, 100, 100,.3); -} - - -.overcast, .showers_scattered{ - background: linear-gradient(127deg, rgba(155, 155, 155, 1), rgba(0, 134, 215, 0) 100%), - linear-gradient(217deg, rgba(155, 155, 155, 1), rgba(0, 134, 215, 1) 100%), - linear-gradient(336deg, rgba(155, 155, 155, 1), rgba(0, 134, 218, 1) 100%); -} - -.gradient-bg{ - background:linear-gradient(0deg, rgba(237,237,237,1) 0%, rgb(203, 221, 225) 100%); -} - -.bg-white{ - background-color: rgb(240, 240, 240); -} - -.custom_card{ - padding:.5rem 1rem; -} - -.bar_container{ - border-radius: 1rem; - background-color: rgb(240, 240, 240); - -} - -.custom_card_forecast_item{ - padding:1.1rem 2rem; - border-radius: .5rem; -} - -.bg_light_grey{ - background-color: #9a9a9a13; -} - - - - - -.body{ - padding:20px; - -} -/* .card{ - padding-left:20px; - padding-right:20px; - padding-top:16px; - padding-bottom:16px; -} */ - - -.card_info{ - background-color: #F6635C; -} -.bold{ - font-weight:600; -} -.title-xl{ - font-size:76px; -} -.title-m{ - font-size:22px; -} -.title-s{ - font-size:18px; -} -.bg-green { - background-color: #00ff00; /* Replace with your desired color */ -} -.bg-orange { - background-color: #F6635C; /* Replace with your desired color */ -} -.bg-pink { - background-color: #FE7BE5; /* Replace with your desired color */ -} diff --git a/src/constants.py b/src/constants.py index 3ccab32..20d67e8 100644 --- a/src/constants.py +++ b/src/constants.py @@ -1,5 +1,3 @@ -import os - icon_loc = "@icon_location@/share/icons/hicolor/scalable/mousam_icons/" diff --git a/src/css.py b/src/css.py deleted file mode 100644 index c7a5a24..0000000 --- a/src/css.py +++ /dev/null @@ -1,263 +0,0 @@ -css = """ -.condition_label { - font-size: 30px; -} -.main_temp_label { - font-size: 4rem; -} - -.btn_sm{ - font-size:1rem; - padding:0.2rem .9rem; -} - - -.text-l1{ - font-size: 4rem; -} -.text-l2{ - font-size: 3.5rem; -} -.text-l3{ - font-size: 3rem; -} -.text-l4{ - font-size: 2.5rem; -} -.text-1{ - font-size: 2rem; -} -.text-2a{ - font-size: 1.8rem; -} -.text-2b{ - font-size: 1.5rem; -} -.text-3{ - font-size: 1.3rem; -} -.text-3a{ - font-size: 1.2rem; -} -.text-4{ - font-size: 1.1rem; -} -.text-5{ - font-size: 1.05rem; -} -.text-6{ - font-size: .95rem; -} -.text-7{ - font-size: 9rem; -} -.text-8{ - font-size: .85rem; -} -.text-9{ - font-size: .8rem; -} - -.title-xl{ - font-size:76px; -} -.title-l{ - font-size:42px; -} -.title-m{ - font-size:22px; -} -.title-s{ - font-size:18px; -} - -.light-1{ - opacity: .95; -} -.light-2{ - opacity: .9; -} -.light-3{ - opacity: .8; -} -.light-4{ - opacity: .75; -} -.light-5{ - opacity: .7; -} -.light-6{ - opacity: .6; -} - - -.bold-1{ - font-weight: 700; -} -.bold-2{ - font-weight: 600; -} -.bold-3{ - font-weight: 500; -} - - -.dark{ - color:#141414; -} - -.main_window{ - border-radius:13px; - border : 1px solid rgba(100, 100, 100,.3); -} - - -.overcast, .showers_scattered{ - background: linear-gradient(127deg, rgba(155, 155, 155, 1), rgba(0, 134, 215, 0) 100%), - linear-gradient(217deg, rgba(155, 155, 155, 1), rgba(0, 134, 215, 1) 100%), - linear-gradient(336deg, rgba(155, 155, 155, 1), rgba(0, 134, 218, 1) 100%); -} - -.gradient-bg{ - background:linear-gradient(0deg, rgba(237,237,237,1) 0%, rgb(203, 221, 225) 100%); -} - -.bg-white{ - background-color: rgb(240, 240, 240); -} - -.custom_card{ - padding:.5rem 1rem; -} - -.bar_container{ - border-radius: 1rem; - background-color: rgb(240, 240, 240); - -} - -.custom_card_forecast_item{ - padding: 0.15rem 1.5rem; - border-radius: .7rem; -} - -.custom_card_hourly{ - border-radius: .5rem; - padding: .7rem .7rem; - margin-top: .3rem; - background-color: rgba(100, 100, 100, .06); -} - -.custom_card_hourly_now{ - padding: .7rem 1.2rem; - margin-top: 0rem; - background-color: rgba(100, 100, 100, .15); - -} -.bg_light_grey{ - background-color: #9a9a9a13; -} - - - - - -.body{ - padding:20px; - -} -/* .card{ - padding-left:20px; - padding-right:20px; - padding-top:16px; - padding-bottom:16px; -} */ - - -.card_info{ - background-color: #F6635C; -} -.bold{ - font-weight:600; -} -.title-xl{ - font-size:76px; -} -.title-m{ - font-size:22px; -} -.title-s{ - font-size:18px; -} -.bg-green { - background-color: #00ff00; /* Replace with your desired color */ -} -.bg-orange { - background-color: #F6635C; /* Replace with your desired color */ -} -.bg-pink { - background-color: #FE7BE5; /* Replace with your desired color */ -} - -.clear_sky, .few_clouds { - background: linear-gradient(127deg, rgba(187, 188, 179, 1), rgba(0, 134, 218, 0) 100%), - linear-gradient(217deg, rgba(187, 188, 179, 1), rgba(0, 174, 258, 1) 100%), - linear-gradient(136deg, rgba(187, 188, 179, 1), rgba(0, 174, 258, 1) 100%); - - -} .overcast, .showers_scattered{ - background: linear-gradient(127deg, rgba(155, 155, 155, 1), rgba(0, 134, 215, 0) 100%), - linear-gradient(217deg, rgba(155, 155, 155, 1), rgba(0, 134, 215, 1) 100%), - linear-gradient(336deg, rgba(155, 155, 155, 1), rgba(0, 134, 218, 1) 100%); - -} .showers_large { - background: linear-gradient(127deg, rgba(134, 137, 154, 1), rgba(134, 137, 154, 0) 100%), - linear-gradient(217deg, rgba(134, 137, 154, 1), rgba(0, 134, 218, 1) 100%), - linear-gradient(336deg, rgba(134, 137, 154, 1), rgba(0, 134, 218, 1) 100%); - -} .storm{ - background: linear-gradient(127deg, rgb(108, 123, 152), rgb(92, 92, 92)), - linear-gradient(217deg, rgb(108, 123, 152), rgb(92, 92, 92)), - linear-gradient(336deg, rgb(108, 123, 152), rgb(92, 92, 92)); - -} .snow{ - background: linear-gradient(135deg, rgb(40, 60, 87) 0%, rgb(80, 104, 133) 30%, rgb(137, 166, 181) 60%, rgb(187, 195, 202) 80%, rgb(187, 195, 202) 100%), - linear-gradient(45deg, rgb(200, 203, 207), rgb(212, 213, 215) 50%, rgba(255, 255, 255, 0.9)); - -} .fog{ - background: linear-gradient(49deg, transparent 0%, rgba(212,195,156,0.43) 100%) 68% 73%/194% 147%, - linear-gradient(137deg, #9cafb0 0%, #c4c3be 70%, #c5c8b5 100%) 72% 55%/174% 123%; - -} - -/* ------------- night --------------- */ - -.clear_sky_night{ - background: linear-gradient(127deg, rgba(45, 48, 72, 1), rgba(23, 27, 60, 0) 100%), - linear-gradient(217deg, rgba(45, 48, 72, 1), rgba(23, 27, 60, 1) 100%); - -}.few_clouds_night{ - background: linear-gradient(to right bottom, #777777, #69696c, #5b5c62, #4d5057, #3e444d, #353c47, #2b3440, #222d3a, #1c2735, #172130, #121b2b, #0e1526); - -} .overcast_night, .showers_scattered_night { - background: linear-gradient(127deg, rgba(27, 29, 40, 1), rgba(23, 27, 60, 0) 100%), - linear-gradient(217deg, rgba(27, 29, 40, 1), rgba(46, 46, 46, 1) 100%); - -} .showers_large_night { - background: linear-gradient(127deg, rgba(27, 29, 40, 1), rgba(23, 27, 60, 0) 100%), - linear-gradient(237deg, rgba(27, 29, 40, 1), rgba(46, 46, 46, 1) 100%); - -} .storm_night{ - background: linear-gradient(127deg, rgba(50, 50, 50, 1), rgba(54, 55, 37, 1) 100%), - linear-gradient(237deg, rgba(50, 50, 50, 1), rgba(54, 55, 37, 1) 100%); - -} .snow_night{ - background: linear-gradient(127deg, rgba(50, 50, 50, 1), rgba(98, 98, 98, 1) 100%), - linear-gradient(217deg, rgba(50, 50, 50, 1), rgba(98, 98, 98, 1) 100%), - linear-gradient(336deg, rgba(50, 50, 50, 1), rgba(98, 98, 98, 1) 100%); - -} .fog_night{ - background: linear-gradient(127deg, rgba(28, 27, 38, 1), rgba(50, 50, 50, 0) 100%), - linear-gradient(217deg, rgba(50, 50, 50, 1), rgba(28, 27, 38, 1) 100%); -} -""" diff --git a/src/css/style.css b/src/css/style.css index e69de29..3f1125d 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -0,0 +1,272 @@ +.condition_label { + font-size: 30px; +} +.main_temp_label { + font-size: 4rem; +} + +.btn_sm{ + font-size:1rem; + padding:0.2rem .9rem; +} + + +.text-l1{ + font-size: 4rem; +} +.text-l2{ + font-size: 3.5rem; +} +.text-l3{ + font-size: 3rem; +} +.text-l4{ + font-size: 2.5rem; +} +.text-1{ + font-size: 2rem; +} +.text-2a{ + font-size: 1.8rem; +} +.text-2b{ + font-size: 1.5rem; +} +.text-3{ + font-size: 1.3rem; +} +.text-3a{ + font-size: 1.2rem; +} +.text-4{ + font-size: 1.1rem; +} +.text-5{ + font-size: 1.05rem; +} +.text-6{ + font-size: .95rem; +} +.text-7{ + font-size: 9rem; +} +.text-8{ + font-size: .85rem; +} +.text-9{ + font-size: .8rem; +} + +.title-xl{ + font-size:76px; +} +.title-l{ + font-size:42px; +} +.title-m{ + font-size:22px; +} +.title-s{ + font-size:18px; +} + +.light-1{ + opacity: .95; +} +.light-2{ + opacity: .9; +} +.light-3{ + opacity: .8; +} +.light-4{ + opacity: .75; +} +.light-5{ + opacity: .7; +} +.light-6{ + opacity: .6; +} + + +.bold-1{ + font-weight: 700; +} +.bold-2{ + font-weight: 600; +} +.bold-3{ + font-weight: 500; +} + + +.dark{ + color:#141414; +} + +.main_window{ + border-radius:13px; + border : 1px solid rgba(100, 100, 100,.3); +} + + +.overcast, .showers_scattered{ + background: linear-gradient(127deg, rgba(155, 155, 155, 1), rgba(0, 134, 215, 0) 100%), + linear-gradient(217deg, rgba(155, 155, 155, 1), rgba(0, 134, 215, 1) 100%), + linear-gradient(336deg, rgba(155, 155, 155, 1), rgba(0, 134, 218, 1) 100%); +} + +.gradient-bg{ + background:linear-gradient(0deg, rgba(237,237,237,1) 0%, rgb(203, 221, 225) 100%); +} + +.bg-white{ + background-color: rgb(240, 240, 240); +} + +.custom_card{ + padding:.5rem 1rem; +} + +.bar_container{ + border-radius: 1rem; + background-color: rgb(240, 240, 240); + +} + +.custom_card_forecast_item{ + padding: 0.15rem 1.5rem; + border-radius: .7rem; +} + +.custom_card_hourly{ + border-radius: .5rem; + padding: .7rem .7rem; + margin-top: .3rem; + background-color: rgba(100, 100, 100, .06); +} + +.custom_card_hourly_now{ + padding: .7rem 1.2rem; + margin-top: 0rem; + background-color: rgba(100, 100, 100, .15); + +} +.bg_light_grey{ + background-color: #9a9a9a13; +} + + + + + +.body{ + padding:20px; + +} +/* .card{ + padding-left:20px; + padding-right:20px; + padding-top:16px; + padding-bottom:16px; +} */ + + +.card_info{ + background-color: #F6635C; +} +.bold{ + font-weight:600; +} +.title-xl{ + font-size:76px; +} +.title-m{ + font-size:22px; +} +.title-s{ + font-size:18px; +} +.bg-green { + background-color: #00ff00; /* Replace with your desired color */ +} +.bg-orange { + background-color: #F6635C; /* Replace with your desired color */ +} +.bg-pink { + background-color: #FE7BE5; /* Replace with your desired color */ +} + +.clear_sky, .few_clouds { + background: linear-gradient(127deg, rgba(187, 188, 179, 1), rgba(0, 134, 218, 0) 100%), + linear-gradient(217deg, rgba(187, 188, 179, 1), rgba(0, 174, 258, 1) 100%), + linear-gradient(136deg, rgba(187, 188, 179, 1), rgba(0, 174, 258, 1) 100%); + + +} +.overcast, .showers_scattered{ + background: linear-gradient(127deg, rgba(155, 155, 155, 1), rgba(0, 134, 215, 0) 100%), + linear-gradient(217deg, rgba(155, 155, 155, 1), rgba(0, 134, 215, 1) 100%), + linear-gradient(336deg, rgba(155, 155, 155, 1), rgba(0, 134, 218, 1) 100%); + +} +.showers_large { + background: linear-gradient(127deg, rgba(134, 137, 154, 1), rgba(134, 137, 154, 0) 100%), + linear-gradient(217deg, rgba(134, 137, 154, 1), rgba(0, 134, 218, 1) 100%), + linear-gradient(336deg, rgba(134, 137, 154, 1), rgba(0, 134, 218, 1) 100%); + +} +.storm{ + background: linear-gradient(127deg, rgb(108, 123, 152), rgb(92, 92, 92)), + linear-gradient(217deg, rgb(108, 123, 152), rgb(92, 92, 92)), + linear-gradient(336deg, rgb(108, 123, 152), rgb(92, 92, 92)); + +} +.snow{ + background: linear-gradient(135deg, rgb(40, 60, 87) 0%, rgb(80, 104, 133) 30%, rgb(137, 166, 181) 60%, rgb(187, 195, 202) 80%, rgb(187, 195, 202) 100%), + linear-gradient(45deg, rgb(200, 203, 207), rgb(212, 213, 215) 50%, rgba(255, 255, 255, 0.9)); + +} +.fog{ + background: linear-gradient(49deg, transparent 0%, rgba(212,195,156,0.43) 100%) 68% 73%/194% 147%, + linear-gradient(137deg, #9cafb0 0%, #c4c3be 70%, #c5c8b5 100%) 72% 55%/174% 123%; + +} + +/* ------------- night --------------- */ + +.clear_sky_night{ + background: linear-gradient(127deg, rgba(45, 48, 72, 1), rgba(23, 27, 60, 0) 100%), + linear-gradient(217deg, rgba(45, 48, 72, 1), rgba(23, 27, 60, 1) 100%); + +} +.few_clouds_night{ + background: linear-gradient(to right bottom, #777777, #69696c, #5b5c62, #4d5057, #3e444d, #353c47, #2b3440, #222d3a, #1c2735, #172130, #121b2b, #0e1526); + +} +.overcast_night, .showers_scattered_night { + background: linear-gradient(127deg, rgba(27, 29, 40, 1), rgba(23, 27, 60, 0) 100%), + linear-gradient(217deg, rgba(27, 29, 40, 1), rgba(46, 46, 46, 1) 100%); + +} +.showers_large_night { + background: linear-gradient(127deg, rgba(27, 29, 40, 1), rgba(23, 27, 60, 0) 100%), + linear-gradient(237deg, rgba(27, 29, 40, 1), rgba(46, 46, 46, 1) 100%); + +} +.storm_night{ + background: linear-gradient(127deg, rgba(50, 50, 50, 1), rgba(54, 55, 37, 1) 100%), + linear-gradient(237deg, rgba(50, 50, 50, 1), rgba(54, 55, 37, 1) 100%); + +} +.snow_night{ + background: linear-gradient(127deg, rgba(50, 50, 50, 1), rgba(98, 98, 98, 1) 100%), + linear-gradient(217deg, rgba(50, 50, 50, 1), rgba(98, 98, 98, 1) 100%), + linear-gradient(336deg, rgba(50, 50, 50, 1), rgba(98, 98, 98, 1) 100%); + +} +.fog_night{ + background: linear-gradient(127deg, rgba(28, 27, 38, 1), rgba(50, 50, 50, 0) 100%), + linear-gradient(217deg, rgba(50, 50, 50, 1), rgba(28, 27, 38, 1) 100%); +} diff --git a/src/frontendCardDayNight.py b/src/frontendCardDayNight.py index d14b031..6748758 100644 --- a/src/frontendCardDayNight.py +++ b/src/frontendCardDayNight.py @@ -23,7 +23,7 @@ def __init__(self): def get_sunset_sunrise_degree(self): from .weatherData import daily_forecast_data as daily_data tz_offset_from_curr_tz = get_tz_offset_by_cord(*get_cords()) - + sunrise_t, sunset_t = 0, 0 for i, data in enumerate(daily_data.time.get("data")): date_ = int(datetime.fromtimestamp(data + my_tz_offset + tz_offset_from_curr_tz).strftime(r"%d")) @@ -42,7 +42,7 @@ def get_sunset_sunrise_degree(self): sunrise_t = datetime.fromtimestamp(sunrise_t + my_tz_offset + tz_offset_from_curr_tz) sunrise_t = sunrise_t.hour + sunrise_t.minute/60 - + sunset_t = datetime.fromtimestamp(sunset_t + my_tz_offset + tz_offset_from_curr_tz) sunset_t = sunset_t.hour + sunset_t.minute/60 diff --git a/src/frontendForecast.py b/src/frontendForecast.py index d4dcd0b..2041c1d 100644 --- a/src/frontendForecast.py +++ b/src/frontendForecast.py @@ -101,7 +101,7 @@ def page_stacks(self, page_name): # Add weather items in the stack-box for i, item in enumerate(items): - + forecast_item_grid = Gtk.Grid(hexpand=True,margin_top=6) forecast_item_grid.set_css_classes( ['bg_light_grey', 'custom_card_forecast_item']) @@ -141,13 +141,12 @@ def page_stacks(self, page_name): forecast_cond_grid = Gtk.Grid(valign=Gtk.Align.CENTER,margin_end = 20) forecast_item_grid.attach(forecast_cond_grid, 2, 0, 1, 1) - + # # prec probability # if page_name == 'tomorrow': # prec_probability = hourly_data.precipitation_probability.get("data")[i] # wind_probability = hourly_data.windspeed_10m.get("data")[i] - - + # drop_icon = Gtk.Image().new_from_file(icons["raindrop"]) # drop_icon.set_pixel_size(25) # drop_icon.set_halign(Gtk.Align.START) @@ -156,7 +155,7 @@ def page_stacks(self, page_name): # prec_probability_label = Gtk.Label(label=f" {prec_probability}%", margin_top=5) # prec_probability_label.set_halign(Gtk.Align.START) # forecast_cond_grid.attach(prec_probability_label, 1, 0, 1, 1) - + # # Wind speed probability # wind_icon = Gtk.Image().new_from_file(icons["wind"]) # wind_icon.set_pixel_size(25) @@ -170,8 +169,7 @@ def page_stacks(self, page_name): # Temp label grid temp_label_grid = Gtk.Grid(valign=Gtk.Align.CENTER) forecast_item_grid.attach(temp_label_grid, 3, 0, 1, 1) - - + # Max temp label ====== temp_max_text = hourly_data.temperature_2m.get("data")[i] if page_name == 'weekly': diff --git a/src/frontendUiDrawBar.py b/src/frontendUiDrawBar.py index 7ed7047..a071ab4 100644 --- a/src/frontendUiDrawBar.py +++ b/src/frontendUiDrawBar.py @@ -35,7 +35,7 @@ def draw(self, area, ctx, h, w, data): filled = self.fill_fr # filled = 1-filled - + lev = y1+(y2-y1)*filled ctx.set_source_rgba(*self.rgb, 0.4) diff --git a/src/frontendUiDrawDayNight.py b/src/frontendUiDrawDayNight.py index 522278b..a0dced5 100644 --- a/src/frontendUiDrawDayNight.py +++ b/src/frontendUiDrawDayNight.py @@ -67,7 +67,6 @@ def on_draw(self, widget, cr, width, height, data): context.line_to(center_x,center_y+outer_radius) context.stroke() - # Clock context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) context.set_font_size(13) @@ -102,8 +101,6 @@ def on_draw(self, widget, cr, width, height, data): # Display the text along the circular path context.show_text(text) - - text2 = "Midnight" # Calculate the position for text placement text_x = center_x - 30 @@ -114,9 +111,6 @@ def on_draw(self, widget, cr, width, height, data): # Display the text along the circular path context.show_text(text2) - - - # Choose sun color if sun_angle>=180 and sun_angle<=360: yellow = abs(1.2-(1-(sun_angle-170)/90)) @@ -162,4 +156,3 @@ def on_draw(self, widget, cr, width, height, data): gap_length = 5 context.set_dash([dash_length, gap_length]) context.stroke() - diff --git a/src/frontendUiDrawbarLine.py b/src/frontendUiDrawbarLine.py index ff5167c..f8667d9 100644 --- a/src/frontendUiDrawbarLine.py +++ b/src/frontendUiDrawbarLine.py @@ -8,7 +8,7 @@ class DrawBar: def __init__(self,value, rgb_color=[0.38, 0.7, 1]): - + self.ht = 45 self.dw = Gtk.DrawingArea() self.dw.set_size_request(50, self.ht+20) @@ -16,13 +16,11 @@ def __init__(self,value, rgb_color=[0.38, 0.7, 1]): self.value = self.ht*value self.rgb = rgb_color - - def draw(self, area, ctx, h, w, data): if self.value == 0: return - + x_offset = 25 y_offset=10 @@ -35,5 +33,3 @@ def draw(self, area, ctx, h, w, data): ctx.move_to(x, y2) ctx.rel_line_to(0, self.ht-y2+y_offset) ctx.stroke() - - diff --git a/src/main.py b/src/main.py index bb051a3..3f0b663 100644 --- a/src/main.py +++ b/src/main.py @@ -17,15 +17,15 @@ # # SPDX-License-Identifier: GPL-3.0-or-later +import os import sys import gi -gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') +gi.require_version('Gtk', '4.0') -from gi.repository import Gtk, Gio,Adw,Gdk +from gi.repository import Gtk, Gio, Adw, Gdk from .mousam import WeatherMainWindow -from .css import css class WeatherApplication(Adw.Application): """The main application singleton class.""" @@ -40,9 +40,13 @@ def __init__(self): def do_activate(self): win = self.props.active_window global css_provider + CSS_PATH = os.path.dirname(os.path.realpath(__file__)) + "/css/" css_provider = Gtk.CssProvider() + Priority = Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + with open(CSS_PATH+'style.css', 'r') as css_file: + css = bytes(css_file.read(), 'utf-8') css_provider.load_from_data(css,len(css)) - Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + Gtk.StyleContext.add_provider_for_display(Gdk.Display.get_default(), css_provider, Priority) launch_maximized = self.settings.get_boolean("launch-maximized") @@ -51,7 +55,7 @@ def do_activate(self): if launch_maximized: win.maximize() - + win.present() def create_action(self, name, callback, shortcuts=None): diff --git a/src/meson.build b/src/meson.build index 9fe7c44..289b560 100644 --- a/src/meson.build +++ b/src/meson.build @@ -49,16 +49,16 @@ mousam_sources = [ 'frontendUiDrawImageIcon.py', 'frontendUiDrawPollutionBar.py', - 'application.css', 'constants.py', 'units.py', 'utils.py', - 'css.py', 'windowAbout.py', 'windowPreferences.py', 'windowLocations.py' ] +install_emptydir(moduledir / 'css') +install_data('css/style.css',install_dir: join_paths(moduledir,'css')) install_data(mousam_sources, install_dir: moduledir) icon_conf = configuration_data() diff --git a/src/mousam.in b/src/mousam.in old mode 100755 new mode 100644 diff --git a/src/mousam.py b/src/mousam.py index 1e64dd2..b14b4eb 100644 --- a/src/mousam.py +++ b/src/mousam.py @@ -7,6 +7,7 @@ from gi.repository import Gtk, Adw, Gio # module import +# from .css import css from .utils import create_toast,check_internet_connection from .windowAbout import AboutWindow from .windowPreferences import WeatherPreferences @@ -31,10 +32,9 @@ class WeatherMainWindow(Gtk.ApplicationWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - global application - self.main_window = application = self + self.main_window = self self.settings = Gio.Settings(schema_id="io.github.amit9838.mousam") - self.set_default_size(1220, 860) + self.set_default_size(800, 600) self.set_title("") # Adding a button into header self.header = Adw.HeaderBar() @@ -58,7 +58,7 @@ def __init__(self, *args, **kwargs): self.hamburger.set_popover(self.popover) self.hamburger.set_icon_name("open-menu-symbolic") # Give it a nice icon self.header.pack_end(self.hamburger) - + # Create a menu button self.location_button = Gtk.Button(label="Open") self.header.pack_end(self.location_button) @@ -79,9 +79,13 @@ def __init__(self, *args, **kwargs): self.add_action(action) menu.append(_("About Mousam"), "win.about") + # Scrollable window + self.scrollable = Gtk.ScrolledWindow() + self.set_child(self.scrollable) + # Toast overlay self.toast_overlay = Adw.ToastOverlay.new() - self.set_child(self.toast_overlay) + self.scrollable.set_child(self.toast_overlay) # Main _clamp self.clamp = Adw.Clamp(maximum_size=1400, tightening_threshold=100) @@ -110,7 +114,6 @@ def show_loader(self): container_loader.set_margin_top(250) container_loader.set_margin_bottom(300) - # Create loader loader = Gtk.Spinner() loader.set_margin_top(50) @@ -120,14 +123,11 @@ def show_loader(self): loader.set_css_classes(['loader']) container_loader.append(loader) - loader_label = Gtk.Label(label=f"Getting Weather Data") loader_label.set_css_classes(["text-2a", "bold-2"]) container_loader.append(loader_label) loader.start() - # loader.set_hexpand(True) - # loader.set_vexpand(True) self.main_stack.add_named(container_loader, "loader") self.main_stack.set_visible_child_name("loader") @@ -142,7 +142,6 @@ def show_error(self,type:str="no_internet",desc : str = ""): message = "Could not fetch data from API" desc = desc icon = "computer-fail-symbolic" - child = self.main_stack.get_child_by_name('error_box') self.toast_overlay.add_toast(create_toast(message,1)) @@ -162,7 +161,7 @@ def show_error(self,type:str="no_internet",desc : str = ""): self.error_label.set_label(message) self.error_label.set_css_classes(["text-1", "bold-2"]) error_box.attach(self.error_label,1,0,1,1) - + self.error_desc = Gtk.Label.new() self.error_desc.set_label(desc) self.error_desc.set_css_classes(["text-4", "bold-4",'light-3']) @@ -178,9 +177,9 @@ def _load_weather_data(self): if not has_internet: self.show_error() return - + self.show_loader() - + # cwd : current_weather_data # cwt : current_weather_thread cwd = threading.Thread(target=fetch_current_weather,name="cwt") @@ -195,12 +194,12 @@ def _load_weather_data(self): apd = threading.Thread(target=fetch_current_air_pollution,name="apt") apd.start() - + apd.join() hfd.join() dfd.join() self.get_weather() - + # =========== Load weather data and create UI ============ def get_weather(self,reload_type=None,title = ""): @@ -211,7 +210,7 @@ def get_weather(self,reload_type=None,title = ""): if len(added_cities) == 0: # Reset city to default if all cities are removed self.settings.reset('added-cities') self.settings.reset('selected-city') - + child = self.main_stack.get_child_by_name('main_grid') if child is not None: self.main_stack.remove(child) @@ -302,7 +301,7 @@ def get_weather(self,reload_type=None,title = ""): # ============= Refresh buttom methods ============== def _refresh_weather(self,widget): - global updated_at + global updated_at # Ignore refreshing weather within 5 second if time.time() - updated_at < 5: @@ -318,12 +317,12 @@ def _refresh_weather(self,widget): # ============= Menu buttom methods ============== def _on_about_clicked(self, *args, **kwargs ): - AboutWindow(application) + AboutWindow(self.main_window) def _on_preferences_clicked(self, *args, **kwargs): - adw_preferences_window = WeatherPreferences(application) + adw_preferences_window = WeatherPreferences(self.main_window) adw_preferences_window.show() def _on_locations_clicked(self, *args, **kwargs): - adw_preferences_window = WeatherLocations(application) - adw_preferences_window.show() \ No newline at end of file + adw_preferences_window = WeatherLocations(self.main_window) + adw_preferences_window.show() diff --git a/src/windowAbout.py b/src/windowAbout.py index 82fe2c6..8266789 100644 --- a/src/windowAbout.py +++ b/src/windowAbout.py @@ -4,19 +4,19 @@ from gi.repository import Gtk, Adw def AboutWindow(parent,*args): - dialog = Adw.AboutWindow.new() - dialog.set_application_name("Mousam") - dialog.set_application_icon("io.github.amit9838.mousam") - dialog.set_version("1.0.1") - dialog.set_developer_name("Amit Chaudhary") - dialog.set_license_type(Gtk.License(Gtk.License.GPL_3_0)) - dialog.set_comments(_("Beautiful and light weight weather app build using Gtk and python")) - dialog.set_website("https://amit9838.github.io/mousam/") - dialog.set_issue_url("https://github.com/amit9838/mousam/issues") - # dialog.add_credit_section("Contributors", ["name url"]) - dialog.set_copyright(_("Copyright © 2024 Mousam Developers")) - dialog.set_developers(["Amit Chaudhary"]) - # Translators: Please enter your credits here. (format: "Name https://example.com" or "Name ", no quotes) - dialog.set_translator_credits(_("translator_credits")) - dialog.set_transient_for(parent) - dialog.present() + dialog = Adw.AboutWindow.new() + dialog.set_application_name("Mousam") + dialog.set_application_icon("io.github.amit9838.mousam") + dialog.set_version("1.0.1") + dialog.set_developer_name("Amit Chaudhary") + dialog.set_license_type(Gtk.License(Gtk.License.GPL_3_0)) + dialog.set_comments(_("Beautiful and light weight weather app build using Gtk and python")) + dialog.set_website("https://amit9838.github.io/mousam/") + dialog.set_issue_url("https://github.com/amit9838/mousam/issues") + # dialog.add_credit_section("Contributors", ["name url"]) + dialog.set_copyright(_("Copyright © 2024 Mousam Developers")) + dialog.set_developers(["Amit Chaudhary"]) + # Translators: Please enter your credits here. (format: "Name https://example.com" or "Name ", no quotes) + dialog.set_translator_credits(_("translator_credits")) + dialog.set_transient_for(parent) + dialog.present() diff --git a/src/windowLocations.py b/src/windowLocations.py index 25cceae..34c6e0e 100644 --- a/src/windowLocations.py +++ b/src/windowLocations.py @@ -13,253 +13,251 @@ updated_at = time.time() class WeatherLocations(Adw.PreferencesWindow): - def __init__(self, application, **kwargs): - super().__init__(**kwargs) - self.application = application - self.set_title(_("Locations")) - self.set_transient_for(application) - self.set_default_size(600, 500) - - # Settings - global selected_city,added_cities,cities - self.settings = application.settings - selected_city = self.settings.get_string('selected-city') - added_cities = self.settings.get_strv('added-cities') - cities = [x.split(',')[0] for x in added_cities] - - # ============= Location Page ============= - location_page = Adw.PreferencesPage() - self.add(location_page) - - self.location_grp = Adw.PreferencesGroup() - self.location_grp.set_title(_("Locations")) - location_page.add(self.location_grp) - - # Add location button with plus icon - add_loc_btn = Gtk.Button() - add_loc_btn_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,valign=Gtk.Align.CENTER,spacing=4) - label = Gtk.Label(label=_("Add")) - add_loc_btn_box.append(label) - - add_icon = Gtk.Image.new_from_icon_name("list-add-symbolic") - add_icon.set_pixel_size(14) - add_loc_btn_box.append(add_icon) - add_loc_btn.set_child(add_loc_btn_box) - add_loc_btn.connect('clicked',self._add_location_dialog) - self.location_grp.set_header_suffix(add_loc_btn) - - self.location_rows = [] - self._create_cities_list(added_cities) - - - # =========== Location page methods ============= - def _create_cities_list(self,data): - if len(self.location_rows)>0: - for action_row in self.location_rows: - self.location_grp.remove(action_row) - self.location_rows.clear() - - for city in added_cities: - button = Gtk.Button() - button.set_icon_name("edit-delete-symbolic") - button.set_css_classes(['circular']) - button.set_tooltip_text(_("Remove location")) - button.set_has_frame(False) - - # Add ckeck icon if city is selected - box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,valign=Gtk.Align.CENTER) - selected_city_index = list(map(lambda city: selected_city in city,added_cities)).index(True) - if added_cities[selected_city_index] == city: - check_icon = Gtk.Image() - check_icon.set_from_icon_name("emblem-ok-symbolic") # Set the icon name and size - check_icon.set_pixel_size(18) - check_icon.set_margin_end(15) - box.append(check_icon) - box.append(button) - - # Location Row - location_row = Adw.ActionRow.new() - location_row.set_activatable(True) - location_row.set_title(f"{city.split(',')[0]},{city.split(',')[1]}") - location_row.set_subtitle(f"{city.split(',')[-2]},{city.split(',')[-1]}") - location_row.add_suffix(box) - - # Location row signal - location_row.connect("activated", self.switch_location) - self.location_rows.append(location_row) - self.location_grp.add(location_row) - - button.connect("clicked", self._remove_city,location_row) - - # ========== Switch Location ============ - def switch_location(self,widget): - global selected_city - title = widget.get_title() - select_cord = f"{widget.get_subtitle()}" - - if len(select_cord.split(",")) < 2: - return - - # Switch if location is not already selected - if selected_city != select_cord: - selected_city = select_cord - self.settings.set_value("selected-city",GLib.Variant("s",selected_city)) - self._create_cities_list(added_cities) - global updated_at - # Ignore refreshing weather within 5 second - - if time.time() - updated_at < 2: - updated_at = time.time() - self.add_toast(create_toast(_("Switch city within 2 seconds is ignored!"),1)) - else: - updated_at = time.time() - self.add_toast(create_toast(_("Selected - {}").format(title),1)) - thread = threading.Thread(target=self.application._load_weather_data,name="load_data") - thread.start() - - # ========== Add Location =========== - def _add_location_dialog(self,application): - - # Create dialog to search and add location - self._dialog = Adw.PreferencesWindow() - self._dialog.set_search_enabled(False) - self._dialog.set_title(title=_('Add New Location')) - self._dialog.set_transient_for(self) - self._dialog.set_default_size(300, 500) - - self._dialog.page = Adw.PreferencesPage() - self._dialog.add(self._dialog.page) - - self._dialog.group = Adw.PreferencesGroup() - self._dialog.page.add(self._dialog.group) - - # Create search box - search_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,valign=Gtk.Align.CENTER, spacing = 6, margin_bottom=10,) - search_box.set_hexpand(True) - self._dialog.group.add(search_box) - - self.search_entry = Gtk.Entry() - self.search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition(1),'edit-clear-symbolic') - self.search_entry.set_placeholder_text(_("Search for a city")) - self.search_entry.set_hexpand(True) - self.search_entry.connect('icon-press',self._clear_search_box) - search_box.append(self.search_entry) - - # Create search button - button = Gtk.Button(label=_("Search")) - button.set_icon_name('system-search-symbolic') - button.set_tooltip_text(_("Search")) - search_box.append(button) - - self._dialog.serach_res_grp = Adw.PreferencesGroup() - self._dialog.serach_res_grp.set_hexpand(True) - self._dialog.group.add(self._dialog.serach_res_grp) - - button.connect("clicked", self._on_find_city_clicked) - self._dialog.search_results = [] - self._dialog.show() - - # ============ Clear Search results ============ - def _clear_search_box(self,widget,pos): - self.search_entry.set_text("") - - # =========== Click on find city =========== - def _on_find_city_clicked(self,widget): - self._find_city(widget) - - # =========== Find city =========== - def _find_city(self,widget): - text = self.search_entry.get_text() - - # Matched city from api - city_data = find_city(text,5) - - if len(self._dialog.search_results)>0: - for action_row in self._dialog.search_results: - self._dialog.serach_res_grp.remove(action_row) - self._dialog.search_results.clear() - - # Plot search results if found - if city_data: - for loc in city_data: - res_row = Adw.ActionRow.new() - res_row.set_activatable(True) - title_arr = [loc.name,loc.state,loc.country] - title_arr = [x for x in title_arr if x is not None] - title = ",".join(title_arr) - res_row.set_title(title) - - # Skip plotting the location in the search results if it has invalid cords - if loc.latitude is None or loc.longitude is None: - continue - if loc.latitude == "" or loc.longitude == "": - continue - - res_row.set_subtitle(f"{loc.latitude},{loc.longitude}") - res_row.connect("activated", self._add_city) - self._dialog.search_results.append(res_row) - self._dialog.serach_res_grp.add(res_row) - - # If no search result is found - else: - res_row = Adw.ActionRow.new() - res_row.set_title(_("No results found !")) - self._dialog.search_results.append(res_row) - self._dialog.serach_res_grp.add(res_row) - - - # =========== Add City on selection =========== - def _add_city(self,widget): - # get title,subtitle from selected item in search result - title = widget.get_title() - title_arr = title.split(',') - modified_title = title_arr[0] - if len(title_arr)>2: - modified_title = f"{title_arr[0]},{title_arr[2]}" - elif len(title_arr)>1: - modified_title = f"{title_arr[0]},{title_arr[1]}" - - loc_city = f"{modified_title},{widget.get_subtitle()}" - - # Add city to db if it is not already added - if loc_city not in added_cities: - added_cities.append(loc_city) - self.settings.set_value("added-cities",GLib.Variant("as",added_cities)) - self._create_cities_list(added_cities) - # self.application.refresh_main_ui() - self._dialog.add_toast(create_toast(_("Added - {0}").format(title),1)) - else: - self._dialog.add_toast(create_toast(_("Location already added!"),1)) - - # ========== Remove City =========== - def _remove_city(self,btn,widget): - global selected_city - city = f"{widget.get_title()},{widget.get_subtitle()}" - - # Don't delete city if only one item is present in the list - if len(added_cities)==1: - self.add_toast(create_toast(_("Add more locations to delete!"),1)) - return - - selected_city_index = list(map(lambda x: selected_city in x, added_cities)).index(True) - s_city = added_cities[selected_city_index] - added_cities.remove(city) - - # If selected city is removed then select first city in the list - if widget.get_subtitle() == selected_city: - first_city = added_cities[0].split(",") - selected_city = f"{first_city[-2]},{first_city[-1]}" - self.settings.set_value("selected-city",GLib.Variant("s",selected_city)) - thread = threading.Thread(target=self.application._load_weather_data,name="load_data") - thread.start() - - - self.settings.set_value("added-cities",GLib.Variant("as",added_cities)) - self._create_cities_list(added_cities) - if s_city == city: # fetch weather only if selected_city was removed - pass - # self.application.refresh_weather(self.application) - else: - pass - # self.application.refresh_main_ui() - self.add_toast(create_toast(_("Deleted - {0}".format(widget.get_title())),1)) + def __init__(self, application, **kwargs): + super().__init__(**kwargs) + self.application = application + self.set_title(_("Locations")) + self.set_transient_for(application) + self.set_default_size(600, 500) + + # Settings + global selected_city,added_cities,cities + self.settings = application.settings + selected_city = self.settings.get_string('selected-city') + added_cities = self.settings.get_strv('added-cities') + cities = [x.split(',')[0] for x in added_cities] + + # ============= Location Page ============= + location_page = Adw.PreferencesPage() + self.add(location_page) + + self.location_grp = Adw.PreferencesGroup() + self.location_grp.set_title(_("Locations")) + location_page.add(self.location_grp) + + # Add location button with plus icon + add_loc_btn = Gtk.Button() + add_loc_btn_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,valign=Gtk.Align.CENTER,spacing=4) + label = Gtk.Label(label=_("Add")) + add_loc_btn_box.append(label) + + add_icon = Gtk.Image.new_from_icon_name("list-add-symbolic") + add_icon.set_pixel_size(14) + add_loc_btn_box.append(add_icon) + add_loc_btn.set_child(add_loc_btn_box) + add_loc_btn.connect('clicked',self._add_location_dialog) + self.location_grp.set_header_suffix(add_loc_btn) + + self.location_rows = [] + self._create_cities_list(added_cities) + + # =========== Location page methods ============= + def _create_cities_list(self,data): + if len(self.location_rows)>0: + for action_row in self.location_rows: + self.location_grp.remove(action_row) + self.location_rows.clear() + + for city in added_cities: + button = Gtk.Button() + button.set_icon_name("edit-delete-symbolic") + button.set_css_classes(['circular']) + button.set_tooltip_text(_("Remove location")) + button.set_has_frame(False) + + # Add ckeck icon if city is selected + box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,valign=Gtk.Align.CENTER) + selected_city_index = list(map(lambda city: selected_city in city,added_cities)).index(True) + if added_cities[selected_city_index] == city: + check_icon = Gtk.Image() + check_icon.set_from_icon_name("emblem-ok-symbolic") # Set the icon name and size + check_icon.set_pixel_size(18) + check_icon.set_margin_end(15) + box.append(check_icon) + box.append(button) + + # Location Row + location_row = Adw.ActionRow.new() + location_row.set_activatable(True) + location_row.set_title(f"{city.split(',')[0]},{city.split(',')[1]}") + location_row.set_subtitle(f"{city.split(',')[-2]},{city.split(',')[-1]}") + location_row.add_suffix(box) + + # Location row signal + location_row.connect("activated", self.switch_location) + self.location_rows.append(location_row) + self.location_grp.add(location_row) + + button.connect("clicked", self._remove_city,location_row) + + # ========== Switch Location ============ + def switch_location(self,widget): + global selected_city + title = widget.get_title() + select_cord = f"{widget.get_subtitle()}" + + if len(select_cord.split(",")) < 2: + return + + # Switch if location is not already selected + if selected_city != select_cord: + selected_city = select_cord + self.settings.set_value("selected-city",GLib.Variant("s",selected_city)) + self._create_cities_list(added_cities) + global updated_at + # Ignore refreshing weather within 5 second + + if time.time() - updated_at < 2: + updated_at = time.time() + self.add_toast(create_toast(_("Switch city within 2 seconds is ignored!"),1)) + else: + updated_at = time.time() + self.add_toast(create_toast(_("Selected - {}").format(title),1)) + thread = threading.Thread(target=self.application._load_weather_data,name="load_data") + thread.start() + + # ========== Add Location =========== + def _add_location_dialog(self,application): + + # Create dialog to search and add location + self._dialog = Adw.PreferencesWindow() + self._dialog.set_search_enabled(False) + self._dialog.set_title(title=_('Add New Location')) + self._dialog.set_transient_for(self) + self._dialog.set_default_size(300, 500) + + self._dialog.page = Adw.PreferencesPage() + self._dialog.add(self._dialog.page) + + self._dialog.group = Adw.PreferencesGroup() + self._dialog.page.add(self._dialog.group) + + # Create search box + search_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,valign=Gtk.Align.CENTER, spacing = 6, margin_bottom=10,) + search_box.set_hexpand(True) + self._dialog.group.add(search_box) + + self.search_entry = Gtk.Entry() + self.search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition(1),'edit-clear-symbolic') + self.search_entry.set_placeholder_text(_("Search for a city")) + self.search_entry.set_hexpand(True) + self.search_entry.connect('icon-press',self._clear_search_box) + search_box.append(self.search_entry) + + # Create search button + button = Gtk.Button(label=_("Search")) + button.set_icon_name('system-search-symbolic') + button.set_tooltip_text(_("Search")) + search_box.append(button) + + self._dialog.serach_res_grp = Adw.PreferencesGroup() + self._dialog.serach_res_grp.set_hexpand(True) + self._dialog.group.add(self._dialog.serach_res_grp) + + button.connect("clicked", self._on_find_city_clicked) + self._dialog.search_results = [] + self._dialog.show() + + # ============ Clear Search results ============ + def _clear_search_box(self,widget,pos): + self.search_entry.set_text("") + + # =========== Click on find city =========== + def _on_find_city_clicked(self,widget): + self._find_city(widget) + + # =========== Find city =========== + def _find_city(self,widget): + text = self.search_entry.get_text() + + # Matched city from api + city_data = find_city(text,5) + + if len(self._dialog.search_results)>0: + for action_row in self._dialog.search_results: + self._dialog.serach_res_grp.remove(action_row) + self._dialog.search_results.clear() + + # Plot search results if found + if city_data: + for loc in city_data: + res_row = Adw.ActionRow.new() + res_row.set_activatable(True) + title_arr = [loc.name,loc.state,loc.country] + title_arr = [x for x in title_arr if x is not None] + title = ",".join(title_arr) + res_row.set_title(title) + + # Skip plotting the location in the search results if it has invalid cords + if loc.latitude is None or loc.longitude is None: + continue + if loc.latitude == "" or loc.longitude == "": + continue + + res_row.set_subtitle(f"{loc.latitude},{loc.longitude}") + res_row.connect("activated", self._add_city) + self._dialog.search_results.append(res_row) + self._dialog.serach_res_grp.add(res_row) + + # If no search result is found + else: + res_row = Adw.ActionRow.new() + res_row.set_title(_("No results found !")) + self._dialog.search_results.append(res_row) + self._dialog.serach_res_grp.add(res_row) + + + # =========== Add City on selection =========== + def _add_city(self,widget): + # get title,subtitle from selected item in search result + title = widget.get_title() + title_arr = title.split(',') + modified_title = title_arr[0] + if len(title_arr)>2: + modified_title = f"{title_arr[0]},{title_arr[2]}" + elif len(title_arr)>1: + modified_title = f"{title_arr[0]},{title_arr[1]}" + + loc_city = f"{modified_title},{widget.get_subtitle()}" + + # Add city to db if it is not already added + if loc_city not in added_cities: + added_cities.append(loc_city) + self.settings.set_value("added-cities",GLib.Variant("as",added_cities)) + self._create_cities_list(added_cities) + # self.application.refresh_main_ui() + self._dialog.add_toast(create_toast(_("Added - {0}").format(title),1)) + else: + self._dialog.add_toast(create_toast(_("Location already added!"),1)) + + # ========== Remove City =========== + def _remove_city(self,btn,widget): + global selected_city + city = f"{widget.get_title()},{widget.get_subtitle()}" + + # Don't delete city if only one item is present in the list + if len(added_cities)==1: + self.add_toast(create_toast(_("Add more locations to delete!"),1)) + return + + selected_city_index = list(map(lambda x: selected_city in x, added_cities)).index(True) + s_city = added_cities[selected_city_index] + added_cities.remove(city) + + # If selected city is removed then select first city in the list + if widget.get_subtitle() == selected_city: + first_city = added_cities[0].split(",") + selected_city = f"{first_city[-2]},{first_city[-1]}" + self.settings.set_value("selected-city",GLib.Variant("s",selected_city)) + thread = threading.Thread(target=self.application._load_weather_data,name="load_data") + thread.start() + + self.settings.set_value("added-cities",GLib.Variant("as",added_cities)) + self._create_cities_list(added_cities) + if s_city == city: # fetch weather only if selected_city was removed + pass + # self.application.refresh_weather(self.application) + else: + pass + # self.application.refresh_main_ui() + self.add_toast(create_toast(_("Deleted - {0}".format(widget.get_title())),1)) diff --git a/src/windowPreferences.py b/src/windowPreferences.py index a42412f..2b58060 100644 --- a/src/windowPreferences.py +++ b/src/windowPreferences.py @@ -14,108 +14,107 @@ class WeatherPreferences(Adw.PreferencesWindow): - def __init__(self, application, **kwargs): - super().__init__(**kwargs) - self.application = application - self.set_transient_for(application) - self.set_default_size(600, 500) - - global selected_city,added_cities,cities,use_personal_api,isValid_personal_api,personal_api_key,measurement_type - self.settings = application.settings - selected_city = self.settings.get_string('selected-city') - added_cities = list(self.settings.get_strv('added-cities')) - # use_gradient = self.settings.get_boolean('use-gradient-bg') - should_launch_maximized = self.settings.get_boolean('launch-maximized') - cities = [x.split(',')[0] for x in added_cities] - measurement_type = get_measurement_type() - - - # =============== Appearance Page =============== - appearance_page = Adw.PreferencesPage() - appearance_page.set_title(_("Appearance")) - appearance_page.set_icon_name('applications-graphics-symbolic') - self.add(appearance_page) - - self.appearance_grp = Adw.PreferencesGroup() - appearance_page.add(self.appearance_grp) - - # Dynamic Background - # gradient_row = Adw.ActionRow.new() - # gradient_row.set_activatable(True) - # gradient_row.set_title(_("Dynamic Background")) - # gradient_row.set_subtitle(_("Background changes based on current weather condition (Restart required)")) - - # self.g_switch_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,valign=Gtk.Align.CENTER) - # self.gradient_switch = Gtk.Switch() - # self.gradient_switch.set_active(True) - # self.gradient_switch.connect("state-set",self._use_gradient_bg) - # self.g_switch_box.append(self.gradient_switch) - # gradient_row.add_suffix(self.g_switch_box) - # self.appearance_grp.add(gradient_row) - - # Launch the app in maximize mode - launch_maximized = Adw.ActionRow.new() - launch_maximized.set_activatable(True) - launch_maximized.set_title(_("Launch Maximized")) - launch_maximized.set_subtitle(_("Launch the weather app in maximized mode (Restart required)")) - - self.g_switch_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,valign=Gtk.Align.CENTER) - self.launch_max_switch = Gtk.Switch() - self.launch_max_switch.set_active(should_launch_maximized) - self.launch_max_switch.connect("state-set",self._on_click_launch_maximixed) - self.g_switch_box.append(self.launch_max_switch) - launch_maximized.add_suffix(self.g_switch_box) - self.appearance_grp.add(launch_maximized) - - # Units and measurement - self.measurement_group = Adw.PreferencesGroup.new() - self.measurement_group.set_margin_top(20) - self.measurement_group.set_title(_('Units & Measurements')) - self.appearance_grp.add(self.measurement_group) - - self.metric_unit = Adw.ActionRow.new() - self.metric_unit.set_title(_('°C')) - self.metric_unit.set_subtitle(_("METRIC system with units like celcuis, km/h, kilometer")) - self.metric_check_btn = Gtk.CheckButton.new() - self.metric_unit.add_prefix(self.metric_check_btn) - self.metric_unit.set_activatable_widget(self.metric_check_btn) - self.metric_unit.connect("activated", self._change_unit,'metric') - self.measurement_group.add(self.metric_unit) - - self.imperial_unit = Adw.ActionRow.new() - self.imperial_unit.set_title(_('°F')) - self.imperial_unit.set_subtitle(_("IMPERIAL system with units like fahrenheit, mph, miles")) - self.imperial_check_btn = Gtk.CheckButton.new() - self.imperial_unit.add_prefix(self.imperial_check_btn) - self.imperial_check_btn.set_group(self.metric_check_btn) - self.imperial_unit.set_activatable_widget(self.imperial_check_btn) - self.imperial_unit.connect("activated", self._change_unit,'imperial') - self.measurement_group.add(self.imperial_unit) - GLib.idle_add(self.metric_unit.activate) if measurement_type == 'metric' else GLib.idle_add(self.imperial_unit.activate) - - - # =============== Appearance Methods =============== - def _use_gradient_bg(self,widget,state): - self.settings.set_value("use-gradient-bg",GLib.Variant("b",state)) - - def _on_click_launch_maximixed(self,widget,state): - self.settings.set_value("launch-maximized",GLib.Variant("b",state)) - - def _change_unit(self,widget,value): - global measurement_type - if measurement_type != value: - self.settings.set_value("measure-type",GLib.Variant("s",value)) - # GLib.idle_add(self.application.refresh_weather,self.application,False) - measurement_type = get_measurement_type() - - # Ignore refreshing weather within 5 second - global updated_at - - if time.time() - updated_at < 2: - updated_at = time.time() - self.add_toast(create_toast(_("Switching within 2 seconds is ignored!"),1)) - else: - updated_at = time.time() - self.add_toast(create_toast(_("Switched to - {}").format(value.capitalize()),1)) - thread = threading.Thread(target=self.application._load_weather_data,name="load_data") - thread.start() + def __init__(self, application, **kwargs): + super().__init__(**kwargs) + self.application = application + self.set_transient_for(application) + self.set_default_size(600, 500) + + global selected_city,added_cities,cities,use_personal_api,isValid_personal_api,personal_api_key,measurement_type + self.settings = application.settings + selected_city = self.settings.get_string('selected-city') + added_cities = list(self.settings.get_strv('added-cities')) + # use_gradient = self.settings.get_boolean('use-gradient-bg') + should_launch_maximized = self.settings.get_boolean('launch-maximized') + cities = [x.split(',')[0] for x in added_cities] + measurement_type = get_measurement_type() + + + # =============== Appearance Page =============== + appearance_page = Adw.PreferencesPage() + appearance_page.set_title(_("Appearance")) + appearance_page.set_icon_name('applications-graphics-symbolic') + self.add(appearance_page) + + self.appearance_grp = Adw.PreferencesGroup() + appearance_page.add(self.appearance_grp) + + # Dynamic Background + # gradient_row = Adw.ActionRow.new() + # gradient_row.set_activatable(True) + # gradient_row.set_title(_("Dynamic Background")) + # gradient_row.set_subtitle(_("Background changes based on current weather condition (Restart required)")) + + # self.g_switch_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,valign=Gtk.Align.CENTER) + # self.gradient_switch = Gtk.Switch() + # self.gradient_switch.set_active(True) + # self.gradient_switch.connect("state-set",self._use_gradient_bg) + # self.g_switch_box.append(self.gradient_switch) + # gradient_row.add_suffix(self.g_switch_box) + # self.appearance_grp.add(gradient_row) + + # Launch the app in maximize mode + launch_maximized = Adw.ActionRow.new() + launch_maximized.set_activatable(True) + launch_maximized.set_title(_("Launch Maximized")) + launch_maximized.set_subtitle(_("Launch the weather app in maximized mode (Restart required)")) + + self.g_switch_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,valign=Gtk.Align.CENTER) + self.launch_max_switch = Gtk.Switch() + self.launch_max_switch.set_active(should_launch_maximized) + self.launch_max_switch.connect("state-set",self._on_click_launch_maximixed) + self.g_switch_box.append(self.launch_max_switch) + launch_maximized.add_suffix(self.g_switch_box) + self.appearance_grp.add(launch_maximized) + + # Units and measurement + self.measurement_group = Adw.PreferencesGroup.new() + self.measurement_group.set_margin_top(20) + self.measurement_group.set_title(_('Units & Measurements')) + self.appearance_grp.add(self.measurement_group) + + self.metric_unit = Adw.ActionRow.new() + self.metric_unit.set_title(_('°C')) + self.metric_unit.set_subtitle(_("METRIC system with units like celcuis, km/h, kilometer")) + self.metric_check_btn = Gtk.CheckButton.new() + self.metric_unit.add_prefix(self.metric_check_btn) + self.metric_unit.set_activatable_widget(self.metric_check_btn) + self.metric_unit.connect("activated", self._change_unit,'metric') + self.measurement_group.add(self.metric_unit) + + self.imperial_unit = Adw.ActionRow.new() + self.imperial_unit.set_title(_('°F')) + self.imperial_unit.set_subtitle(_("IMPERIAL system with units like fahrenheit, mph, miles")) + self.imperial_check_btn = Gtk.CheckButton.new() + self.imperial_unit.add_prefix(self.imperial_check_btn) + self.imperial_check_btn.set_group(self.metric_check_btn) + self.imperial_unit.set_activatable_widget(self.imperial_check_btn) + self.imperial_unit.connect("activated", self._change_unit,'imperial') + self.measurement_group.add(self.imperial_unit) + GLib.idle_add(self.metric_unit.activate) if measurement_type == 'metric' else GLib.idle_add(self.imperial_unit.activate) + + # =============== Appearance Methods =============== + def _use_gradient_bg(self,widget,state): + self.settings.set_value("use-gradient-bg",GLib.Variant("b",state)) + + def _on_click_launch_maximixed(self,widget,state): + self.settings.set_value("launch-maximized",GLib.Variant("b",state)) + + def _change_unit(self,widget,value): + global measurement_type + if measurement_type != value: + self.settings.set_value("measure-type",GLib.Variant("s",value)) + # GLib.idle_add(self.application.refresh_weather,self.application,False) + measurement_type = get_measurement_type() + + # Ignore refreshing weather within 5 second + global updated_at + + if time.time() - updated_at < 2: + updated_at = time.time() + self.add_toast(create_toast(_("Switching within 2 seconds is ignored!"),1)) + else: + updated_at = time.time() + self.add_toast(create_toast(_("Switched to - {}").format(value.capitalize()),1)) + thread = threading.Thread(target=self.application._load_weather_data,name="load_data") + thread.start()