Serenity Operating System
1/*
2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "ClockWidget.h"
8#include <LibConfig/Client.h>
9#include <LibGUI/Action.h>
10#include <LibGUI/Menu.h>
11#include <LibGUI/Painter.h>
12#include <LibGUI/Process.h>
13#include <LibGUI/SeparatorWidget.h>
14#include <LibGUI/Window.h>
15#include <LibGfx/Bitmap.h>
16#include <LibGfx/Font/FontDatabase.h>
17#include <LibGfx/Palette.h>
18
19namespace Taskbar {
20
21ClockWidget::ClockWidget()
22{
23 set_frame_shape(Gfx::FrameShape::Box);
24 set_frame_shadow(Gfx::FrameShadow::Sunken);
25 set_frame_thickness(1);
26
27 update_format(Config::read_string("Taskbar"sv, "Clock"sv, "TimeFormat"sv, "%T"sv));
28
29 m_timer = add<Core::Timer>(1000, [this] {
30 static time_t last_update_time;
31 time_t now = time(nullptr);
32 if (now != last_update_time) {
33 tick_clock();
34 last_update_time = now;
35 set_tooltip(Core::DateTime::now().to_deprecated_string("%Y-%m-%d"sv));
36 }
37 });
38 m_timer->start();
39
40 m_calendar_window = add<GUI::Window>(window());
41 m_calendar_window->set_window_type(GUI::WindowType::Popup);
42 m_calendar_window->resize(m_window_size.width(), m_window_size.height());
43
44 auto root_container = m_calendar_window->set_main_widget<GUI::Frame>().release_value_but_fixme_should_propagate_errors();
45 root_container->set_fill_with_background_color(true);
46 root_container->set_layout<GUI::VerticalBoxLayout>(GUI::Margins { 2, 0 }, 0);
47 root_container->set_frame_shape(Gfx::FrameShape::Window);
48
49 auto& navigation_container = root_container->add<GUI::Widget>();
50 navigation_container.set_fixed_height(24);
51 navigation_container.set_layout<GUI::HorizontalBoxLayout>(GUI::Margins { 2 });
52
53 m_prev_date = navigation_container.add<GUI::Button>();
54 m_prev_date->set_button_style(Gfx::ButtonStyle::Coolbar);
55 m_prev_date->set_fixed_size(24, 24);
56 m_prev_date->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"sv).release_value_but_fixme_should_propagate_errors());
57 m_prev_date->on_click = [&](auto) {
58 unsigned view_month = m_calendar->view_month();
59 unsigned view_year = m_calendar->view_year();
60 if (m_calendar->mode() == GUI::Calendar::Month) {
61 view_month--;
62 if (m_calendar->view_month() == 1) {
63 view_month = 12;
64 view_year--;
65 }
66 } else {
67 view_year--;
68 }
69 m_calendar->update_tiles(view_year, view_month);
70 if (m_calendar->mode() == GUI::Calendar::Year)
71 m_selected_calendar_button->set_text(m_calendar->formatted_date(GUI::Calendar::YearOnly).release_value_but_fixme_should_propagate_errors());
72 else
73 m_selected_calendar_button->set_text(m_calendar->formatted_date().release_value_but_fixme_should_propagate_errors());
74 };
75
76 m_selected_calendar_button = navigation_container.add<GUI::Button>();
77 m_selected_calendar_button->set_button_style(Gfx::ButtonStyle::Coolbar);
78 m_selected_calendar_button->set_fixed_height(24);
79 m_selected_calendar_button->on_click = [&](auto) {
80 m_calendar->toggle_mode();
81 if (m_calendar->mode() == GUI::Calendar::Year)
82 m_selected_calendar_button->set_text(m_calendar->formatted_date(GUI::Calendar::YearOnly).release_value_but_fixme_should_propagate_errors());
83 else
84 m_selected_calendar_button->set_text(m_calendar->formatted_date().release_value_but_fixme_should_propagate_errors());
85 };
86
87 m_next_date = navigation_container.add<GUI::Button>();
88 m_next_date->set_button_style(Gfx::ButtonStyle::Coolbar);
89 m_next_date->set_fixed_size(24, 24);
90 m_next_date->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"sv).release_value_but_fixme_should_propagate_errors());
91 m_next_date->on_click = [&](auto) {
92 unsigned view_month = m_calendar->view_month();
93 unsigned view_year = m_calendar->view_year();
94 if (m_calendar->mode() == GUI::Calendar::Month) {
95 view_month++;
96 if (m_calendar->view_month() == 12) {
97 view_month = 1;
98 view_year++;
99 }
100 } else {
101 view_year++;
102 }
103 m_calendar->update_tiles(view_year, view_month);
104 if (m_calendar->mode() == GUI::Calendar::Year)
105 m_selected_calendar_button->set_text(m_calendar->formatted_date(GUI::Calendar::YearOnly).release_value_but_fixme_should_propagate_errors());
106 else
107 m_selected_calendar_button->set_text(m_calendar->formatted_date().release_value_but_fixme_should_propagate_errors());
108 };
109
110 auto& separator1 = root_container->add<GUI::HorizontalSeparator>();
111 separator1.set_fixed_height(2);
112
113 auto& calendar_container = root_container->add<GUI::Widget>();
114 calendar_container.set_layout<GUI::HorizontalBoxLayout>(GUI::Margins { 2 });
115
116 m_calendar = calendar_container.add<GUI::Calendar>();
117 m_selected_calendar_button->set_text(m_calendar->formatted_date().release_value_but_fixme_should_propagate_errors());
118
119 m_calendar->on_tile_click = [&] {
120 m_selected_calendar_button->set_text(m_calendar->formatted_date().release_value_but_fixme_should_propagate_errors());
121 };
122
123 m_calendar->on_month_click = [&] {
124 m_selected_calendar_button->set_text(m_calendar->formatted_date().release_value_but_fixme_should_propagate_errors());
125 };
126
127 auto& separator2 = root_container->add<GUI::HorizontalSeparator>();
128 separator2.set_fixed_height(2);
129
130 auto& settings_container = root_container->add<GUI::Widget>();
131 settings_container.set_fixed_height(24);
132 settings_container.set_layout<GUI::HorizontalBoxLayout>(GUI::Margins { 2 });
133 settings_container.add_spacer().release_value_but_fixme_should_propagate_errors();
134
135 m_jump_to_button = settings_container.add<GUI::Button>();
136 m_jump_to_button->set_button_style(Gfx::ButtonStyle::Coolbar);
137 m_jump_to_button->set_fixed_size(24, 24);
138 m_jump_to_button->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/calendar-date.png"sv).release_value_but_fixme_should_propagate_errors());
139 m_jump_to_button->set_tooltip("Jump to today");
140 m_jump_to_button->on_click = [this](auto) {
141 jump_to_current_date();
142 };
143
144 m_calendar_launcher = settings_container.add<GUI::Button>();
145 m_calendar_launcher->set_button_style(Gfx::ButtonStyle::Coolbar);
146 m_calendar_launcher->set_fixed_size(24, 24);
147 m_calendar_launcher->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-calendar.png"sv).release_value_but_fixme_should_propagate_errors());
148 m_calendar_launcher->set_tooltip("Calendar");
149 m_calendar_launcher->on_click = [this](auto) {
150 GUI::Process::spawn_or_show_error(window(), "/bin/Calendar"sv);
151 };
152}
153
154void ClockWidget::update_format(DeprecatedString const& format)
155{
156 m_time_format = format;
157 m_time_width = font().width(Core::DateTime::create(122, 2, 22, 22, 22, 22).to_deprecated_string(format));
158 set_fixed_size(m_time_width + 20, 21);
159}
160
161void ClockWidget::paint_event(GUI::PaintEvent& event)
162{
163 GUI::Frame::paint_event(event);
164 auto time_text = Core::DateTime::now().to_deprecated_string(m_time_format);
165 GUI::Painter painter(*this);
166 painter.add_clip_rect(frame_inner_rect());
167
168 // Render string center-left aligned, but attempt to center the string based on a constant
169 // "ideal" time string (i.e., the same one used to size this widget in the initializer).
170 // This prevents the rest of the string from shifting around while seconds tick.
171 Gfx::Font const& font = Gfx::FontDatabase::default_font();
172 int const frame_width = frame_thickness();
173 int const ideal_width = m_time_width;
174 int const widget_width = max_width().as_int();
175 int const translation_x = (widget_width - ideal_width) / 2 - frame_width;
176
177 painter.draw_text(frame_inner_rect().translated(translation_x, frame_width), time_text, font, Gfx::TextAlignment::CenterLeft, palette().window_text());
178}
179
180void ClockWidget::mousedown_event(GUI::MouseEvent& event)
181{
182 if (event.button() != GUI::MouseButton::Primary) {
183 return;
184 } else {
185 if (!m_calendar_window->is_visible())
186 open();
187 else
188 close();
189 }
190}
191
192void ClockWidget::context_menu_event(GUI::ContextMenuEvent& event)
193{
194 if (!m_context_menu) {
195 m_context_menu = GUI::Menu::construct();
196
197 auto settings_icon = MUST(Gfx::Bitmap::load_from_file("/res/icons/16x16/settings.png"sv));
198 auto open_clock_settings_action = GUI::Action::create("Clock &Settings", *settings_icon, [this](auto&) {
199 GUI::Process::spawn_or_show_error(window(), "/bin/ClockSettings"sv, Array { "--open-tab", "clock" });
200 });
201
202 m_context_menu->add_action(open_clock_settings_action);
203 }
204
205 m_context_menu->popup(event.screen_position());
206}
207
208void ClockWidget::open()
209{
210 jump_to_current_date();
211 position_calendar_window();
212 m_calendar_window->show();
213}
214
215void ClockWidget::close()
216{
217 m_calendar_window->hide();
218}
219
220void ClockWidget::position_calendar_window()
221{
222 constexpr auto taskbar_top_padding { 4 };
223 m_calendar_window->set_rect(
224 screen_relative_rect().right() - m_calendar_window->width() + 1,
225 screen_relative_rect().top() - taskbar_top_padding - m_calendar_window->height(),
226 m_window_size.width(),
227 m_window_size.height());
228}
229
230void ClockWidget::jump_to_current_date()
231{
232 if (m_calendar->mode() == GUI::Calendar::Year)
233 m_calendar->toggle_mode();
234 m_calendar->set_selected_date(Core::DateTime::now());
235 m_calendar->update_tiles(Core::DateTime::now().year(), Core::DateTime::now().month());
236 m_selected_calendar_button->set_text(m_calendar->formatted_date().release_value_but_fixme_should_propagate_errors());
237}
238
239}