Serenity Operating System
at master 170 lines 6.6 kB view raw
1/* 2 * Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "TimeZoneSettingsWidget.h" 8#include <AK/Time.h> 9#include <Applications/ClockSettings/TimeZoneSettingsWidgetGML.h> 10#include <LibGUI/ComboBox.h> 11#include <LibGUI/Event.h> 12#include <LibGUI/ImageWidget.h> 13#include <LibGUI/ItemListModel.h> 14#include <LibGUI/Label.h> 15#include <LibGUI/Layout.h> 16#include <LibGUI/Margins.h> 17#include <LibGUI/Painter.h> 18#include <LibGUI/Process.h> 19#include <LibGfx/Palette.h> 20#include <LibLocale/DateTimeFormat.h> 21#include <LibLocale/Locale.h> 22#include <LibTimeZone/TimeZone.h> 23#include <math.h> 24#include <spawn.h> 25#include <unistd.h> 26 27using StringViewListModel = GUI::ItemListModel<StringView, ReadonlySpan<StringView>>; 28 29static constexpr auto PI_OVER_180 = M_PIf32 / 180.0f; 30static constexpr auto PI_OVER_4 = M_PIf32 / 4.0f; 31static constexpr auto TAU = M_PIf32 * 2.0f; 32 33// The map as stored on disk is a valid Mercadian projected map. But it has quite a bit of dead space that 34// we can remove. This makes the map non-Mercadian, so we need to adjust our math based on what we removed. 35static constexpr auto TIME_ZONE_MAP_NORTHERN_TRIM = 78; 36static constexpr auto TIME_ZONE_MAP_SOUTHERN_TRIM = 50; 37 38static constexpr auto TIME_ZONE_TEXT_WIDTH = 210; 39static constexpr auto TIME_ZONE_TEXT_HEIGHT = 40; 40static constexpr auto TIME_ZONE_TEXT_PADDING = 5; 41static constexpr auto TIME_ZONE_TEXT_COLOR = Gfx::Color::from_rgb(0xeaf688); 42 43ErrorOr<NonnullRefPtr<TimeZoneSettingsWidget>> TimeZoneSettingsWidget::create() 44{ 45 auto timezonesettings_widget = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) TimeZoneSettingsWidget)); 46 47 auto time_zone_map_bitmap = TRY(Gfx::Bitmap::load_from_file("/res/graphics/map.png"sv)); 48 auto time_zone_rect = time_zone_map_bitmap->rect().shrunken(TIME_ZONE_MAP_NORTHERN_TRIM, 0, TIME_ZONE_MAP_SOUTHERN_TRIM, 0); 49 time_zone_map_bitmap = TRY(time_zone_map_bitmap->cropped(time_zone_rect)); 50 51 timezonesettings_widget->m_time_zone_map = *timezonesettings_widget->find_descendant_of_type_named<GUI::ImageWidget>("time_zone_map"); 52 timezonesettings_widget->m_time_zone_map->set_bitmap(time_zone_map_bitmap); 53 54 auto time_zone_marker = TRY(Gfx::Bitmap::load_from_file("/res/icons/32x32/ladyball.png"sv)); 55 timezonesettings_widget->m_time_zone_marker = TRY(time_zone_marker->scaled(0.75f, 0.75f)); 56 57 timezonesettings_widget->set_time_zone_location(); 58 59 return timezonesettings_widget; 60} 61 62TimeZoneSettingsWidget::TimeZoneSettingsWidget() 63{ 64 load_from_gml(time_zone_settings_widget_gml).release_value_but_fixme_should_propagate_errors(); 65 66 static auto time_zones = TimeZone::all_time_zones(); 67 m_time_zone = TimeZone::system_time_zone(); 68 69 m_time_zone_combo_box = *find_descendant_of_type_named<GUI::ComboBox>("time_zone_input"); 70 m_time_zone_combo_box->set_only_allow_values_from_model(true); 71 m_time_zone_combo_box->set_model(*StringViewListModel::create(time_zones)); 72 m_time_zone_combo_box->set_text(m_time_zone); 73 m_time_zone_combo_box->on_change = [&](auto, auto) { 74 set_modified(true); 75 }; 76} 77 78void TimeZoneSettingsWidget::second_paint_event(GUI::PaintEvent& event) 79{ 80 GUI::Widget::second_paint_event(event); 81 82 if (!m_time_zone_location.has_value()) 83 return; 84 85 GUI::Painter painter(*this); 86 painter.add_clip_rect(event.rect()); 87 painter.add_clip_rect(m_time_zone_map->relative_rect()); 88 89 auto x = m_time_zone_map->x() + m_time_zone_map->parent_widget()->layout()->margins().left(); 90 auto y = m_time_zone_map->y() + m_time_zone_map->parent_widget()->layout()->margins().top(); 91 92 auto point = m_time_zone_location->to_type<int>().translated(x, y); 93 point.translate_by(-m_time_zone_marker->width() / 2, -m_time_zone_marker->height() / 2); 94 painter.blit(point, *m_time_zone_marker, rect()); 95 96 point = m_time_zone_location->to_type<int>().translated(x, y); 97 point.translate_by(0, -TIME_ZONE_TEXT_HEIGHT / 2); 98 99 if (point.x() <= (m_time_zone_map->width() / 2)) 100 point.translate_by(m_time_zone_marker->width() / 2 + TIME_ZONE_TEXT_PADDING, 0); 101 else 102 point.translate_by(-m_time_zone_marker->width() / 2 - TIME_ZONE_TEXT_PADDING - TIME_ZONE_TEXT_WIDTH, 0); 103 104 auto text_area = Gfx::IntRect { point.x(), point.y(), TIME_ZONE_TEXT_WIDTH, TIME_ZONE_TEXT_HEIGHT }; 105 painter.draw_rect(text_area, palette().active_window_border1()); 106 107 text_area.shrink(2, 2); 108 painter.fill_rect(text_area, TIME_ZONE_TEXT_COLOR); 109 painter.draw_text(text_area, m_time_zone_text, Gfx::TextAlignment::Center); 110} 111 112void TimeZoneSettingsWidget::reset_default_values() 113{ 114 m_time_zone = "UTC"sv; 115 m_time_zone_combo_box->set_text(m_time_zone); 116 m_time_zone_location.clear(); 117 118 set_time_zone(); 119 update(); 120} 121 122void TimeZoneSettingsWidget::apply_settings() 123{ 124 m_time_zone = m_time_zone_combo_box->text(); 125 126 set_time_zone_location(); 127 set_time_zone(); 128 update(); 129} 130 131void TimeZoneSettingsWidget::set_time_zone_location() 132{ 133 m_time_zone_location = compute_time_zone_location(); 134 135 auto locale = Locale::default_locale(); 136 auto now = AK::Time::now_realtime(); 137 138 auto name = Locale::format_time_zone(locale, m_time_zone, Locale::CalendarPatternStyle::Long, now).release_value_but_fixme_should_propagate_errors(); 139 auto offset = Locale::format_time_zone(locale, m_time_zone, Locale::CalendarPatternStyle::LongOffset, now).release_value_but_fixme_should_propagate_errors(); 140 141 m_time_zone_text = DeprecatedString::formatted("{}\n({})", name, offset); 142} 143 144// https://en.wikipedia.org/wiki/Mercator_projection#Derivation 145Optional<Gfx::FloatPoint> TimeZoneSettingsWidget::compute_time_zone_location() const 146{ 147 auto location = TimeZone::get_time_zone_location(m_time_zone); 148 if (!location.has_value()) 149 return {}; 150 151 auto latitude = location->latitude.decimal_coordinate(); 152 auto longitude = location->longitude.decimal_coordinate(); 153 154 auto rect = m_time_zone_map->bitmap()->rect().to_type<float>(); 155 156 latitude = logf(tanf(PI_OVER_4 + (latitude * PI_OVER_180 / 2.0f))); 157 158 auto mercadian_x = (longitude + 180.0f) * (rect.width() / 360.0f); 159 auto mercadian_y = (rect.height() / 2.0f) - (rect.width() * latitude / TAU); 160 161 mercadian_y -= TIME_ZONE_MAP_NORTHERN_TRIM / 2; 162 mercadian_y += TIME_ZONE_MAP_SOUTHERN_TRIM / 2; 163 164 return Gfx::FloatPoint { mercadian_x, mercadian_y }; 165} 166 167void TimeZoneSettingsWidget::set_time_zone() 168{ 169 GUI::Process::spawn_or_show_error(window(), "/bin/timezone"sv, Array { m_time_zone.characters() }); 170}