ClockSettingsWidget.cpp 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /*
  2. * Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "ClockSettingsWidget.h"
  7. #include <Applications/ClockSettings/ClockSettingsWidgetGML.h>
  8. #include <LibGUI/ComboBox.h>
  9. #include <LibGUI/Event.h>
  10. #include <LibGUI/ImageWidget.h>
  11. #include <LibGUI/ItemListModel.h>
  12. #include <LibGUI/Label.h>
  13. #include <LibGUI/Layout.h>
  14. #include <LibGUI/Margins.h>
  15. #include <LibGUI/Painter.h>
  16. #include <LibGfx/Palette.h>
  17. #include <LibTimeZone/TimeZone.h>
  18. #include <math.h>
  19. #include <spawn.h>
  20. #include <unistd.h>
  21. using StringViewListModel = GUI::ItemListModel<StringView, Span<StringView const>>;
  22. static constexpr auto PI_OVER_180 = M_PIf32 / 180.0f;
  23. static constexpr auto PI_OVER_4 = M_PIf32 / 4.0f;
  24. static constexpr auto TAU = M_PIf32 * 2.0f;
  25. // The map as stored on disk is a valid Mercadian projected map. But it has quite a bit of dead space that
  26. // we can remove. This makes the map non-Mercadian, so we need to adjust our math based on what we removed.
  27. static constexpr auto TIME_ZONE_MAP_NORTHERN_TRIM = 78;
  28. static constexpr auto TIME_ZONE_MAP_SOUTHERN_TRIM = 50;
  29. ClockSettingsWidget::ClockSettingsWidget()
  30. {
  31. load_from_gml(clock_settings_widget_gml);
  32. static auto time_zones = TimeZone::all_time_zones();
  33. m_time_zone = TimeZone::system_time_zone();
  34. m_time_zone_combo_box = *find_descendant_of_type_named<GUI::ComboBox>("time_zone_input");
  35. m_time_zone_combo_box->set_only_allow_values_from_model(true);
  36. m_time_zone_combo_box->set_model(*StringViewListModel::create(time_zones));
  37. m_time_zone_combo_box->set_text(m_time_zone);
  38. auto time_zone_map_bitmap = Gfx::Bitmap::try_load_from_file("/res/graphics/map.png"sv).release_value_but_fixme_should_propagate_errors();
  39. auto time_zone_rect = time_zone_map_bitmap->rect().shrunken(TIME_ZONE_MAP_NORTHERN_TRIM, 0, TIME_ZONE_MAP_SOUTHERN_TRIM, 0);
  40. time_zone_map_bitmap = time_zone_map_bitmap->cropped(time_zone_rect).release_value_but_fixme_should_propagate_errors();
  41. m_time_zone_map = *find_descendant_of_type_named<GUI::ImageWidget>("time_zone_map");
  42. m_time_zone_map->set_bitmap(time_zone_map_bitmap);
  43. auto time_zone_marker = Gfx::Bitmap::try_load_from_file("/res/icons/32x32/ladyball.png").release_value_but_fixme_should_propagate_errors();
  44. m_time_zone_marker = time_zone_marker->scaled(0.75f, 0.75f).release_value_but_fixme_should_propagate_errors();
  45. set_time_zone_location();
  46. }
  47. void ClockSettingsWidget::second_paint_event(GUI::PaintEvent& event)
  48. {
  49. GUI::Widget::second_paint_event(event);
  50. if (!m_time_zone_location.has_value())
  51. return;
  52. GUI::Painter painter(*this);
  53. painter.add_clip_rect(event.rect());
  54. painter.add_clip_rect(m_time_zone_map->relative_rect());
  55. auto x = m_time_zone_map->x() + m_time_zone_map->parent_widget()->layout()->margins().left();
  56. auto y = m_time_zone_map->y() + m_time_zone_map->parent_widget()->layout()->margins().top();
  57. auto point = m_time_zone_location->to_type<int>().translated(x, y);
  58. point.translate_by(-m_time_zone_marker->width() / 2, -m_time_zone_marker->height() / 2);
  59. painter.blit(point, *m_time_zone_marker, rect());
  60. }
  61. void ClockSettingsWidget::reset_default_values()
  62. {
  63. m_time_zone = "UTC"sv;
  64. m_time_zone_combo_box->set_text(m_time_zone);
  65. m_time_zone_location.clear();
  66. set_time_zone();
  67. update();
  68. }
  69. void ClockSettingsWidget::apply_settings()
  70. {
  71. m_time_zone = m_time_zone_combo_box->text();
  72. set_time_zone_location();
  73. set_time_zone();
  74. update();
  75. }
  76. void ClockSettingsWidget::set_time_zone_location()
  77. {
  78. m_time_zone_location = compute_time_zone_location();
  79. }
  80. // https://en.wikipedia.org/wiki/Mercator_projection#Derivation
  81. Optional<Gfx::FloatPoint> ClockSettingsWidget::compute_time_zone_location() const
  82. {
  83. auto location = TimeZone::get_time_zone_location(m_time_zone);
  84. if (!location.has_value())
  85. return {};
  86. auto latitude = location->latitude.decimal_coordinate();
  87. auto longitude = location->longitude.decimal_coordinate();
  88. auto rect = m_time_zone_map->bitmap()->rect().to_type<float>();
  89. latitude = logf(tanf(PI_OVER_4 + (latitude * PI_OVER_180 / 2.0f)));
  90. auto mercadian_x = (longitude + 180.0f) * (rect.width() / 360.0f);
  91. auto mercadian_y = (rect.height() / 2.0f) - (rect.width() * latitude / TAU);
  92. mercadian_y -= TIME_ZONE_MAP_NORTHERN_TRIM / 2;
  93. mercadian_y += TIME_ZONE_MAP_SOUTHERN_TRIM / 2;
  94. return Gfx::FloatPoint { mercadian_x, mercadian_y };
  95. }
  96. void ClockSettingsWidget::set_time_zone() const
  97. {
  98. pid_t child_pid = 0;
  99. char const* argv[] = { "/bin/timezone", m_time_zone.characters(), nullptr };
  100. if ((errno = posix_spawn(&child_pid, "/bin/timezone", nullptr, nullptr, const_cast<char**>(argv), environ))) {
  101. perror("posix_spawn");
  102. exit(1);
  103. }
  104. }