Serenity Operating System
1/*
2 * Copyright (c) 2019-2020, Ryan Grieb <ryan.m.grieb@gmail.com>
3 * Copyright (c) 2020-2022, the SerenityOS developers.
4 * Copyright (c) 2022, Tobias Christiansen <tobyase@serenityos.org>
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 */
8
9#include <AK/DateConstants.h>
10#include <AK/String.h>
11#include <LibConfig/Client.h>
12#include <LibCore/DateTime.h>
13#include <LibGUI/Calendar.h>
14#include <LibGUI/Painter.h>
15#include <LibGUI/Window.h>
16#include <LibGfx/Font/FontDatabase.h>
17#include <LibGfx/Palette.h>
18
19REGISTER_WIDGET(GUI, Calendar);
20
21namespace GUI {
22
23static auto const extra_large_font = Gfx::BitmapFont::load_from_file("/res/fonts/MarietaRegular36.font");
24static auto const large_font = Gfx::BitmapFont::load_from_file("/res/fonts/MarietaRegular24.font");
25static auto const medium_font = Gfx::BitmapFont::load_from_file("/res/fonts/PebbletonRegular14.font");
26static auto const small_font = Gfx::BitmapFont::load_from_file("/res/fonts/KaticaRegular10.font");
27
28Calendar::Calendar(Core::DateTime date_time, Mode mode)
29 : m_selected_date(date_time)
30 , m_mode(mode)
31{
32 auto first_day_of_week = Config::read_string("Calendar"sv, "View"sv, "FirstDayOfWeek"sv, "Sunday"sv);
33 m_first_day_of_week = static_cast<DayOfWeek>(day_of_week_index(first_day_of_week));
34
35 auto first_day_of_weekend = Config::read_string("Calendar"sv, "View"sv, "FirstDayOfWeekend"sv, "Saturday"sv);
36 m_first_day_of_weekend = static_cast<DayOfWeek>(day_of_week_index(first_day_of_weekend));
37
38 auto weekend_length = Config::read_i32("Calendar"sv, "View"sv, "WeekendLength"sv, 2);
39 m_weekend_length = weekend_length;
40
41 set_fill_with_background_color(true);
42
43 for (int i = 0; i < 7; i++) {
44 Day day;
45 m_days.append(move(day));
46 }
47 for (int i = 0; i < 12; i++) {
48 MonthTile month;
49 m_months.append(move(month));
50 for (int j = 0; j < 42; j++) {
51 Tile tile;
52 m_tiles[i].append(move(tile));
53 }
54 }
55
56 auto default_view = Config::read_string("Calendar"sv, "View"sv, "DefaultView"sv, "Month"sv);
57 if (default_view == "Year") {
58 m_mode = Year;
59 m_show_days = false;
60 m_show_year = true;
61 m_show_month_year = true;
62 }
63
64 update_tiles(m_selected_date.year(), m_selected_date.month());
65}
66
67void Calendar::set_grid(bool show)
68{
69 if (m_grid == show)
70 return;
71 m_grid = show;
72}
73
74void Calendar::toggle_mode()
75{
76 m_mode == Month ? m_mode = Year : m_mode = Month;
77 set_show_days_of_the_week(!m_show_days);
78 set_show_year(!m_show_year);
79 set_show_month_and_year(!m_show_month_year);
80 update_tiles(this->view_year(), this->view_month());
81 this->resize(this->height(), this->width());
82 invalidate_layout();
83}
84
85void Calendar::resize_event(GUI::ResizeEvent& event)
86{
87 m_event_size.set_width(event.size().width() - (frame_thickness() * 2));
88 m_event_size.set_height(event.size().height() - (frame_thickness() * 2));
89
90 if (mode() == Month) {
91 if (m_event_size.width() < 160 || m_event_size.height() < 130)
92 set_show_month_and_year(false);
93 else if (m_event_size.width() >= 160 && m_event_size.height() >= 130)
94 set_show_month_and_year(true);
95
96 set_show_year(false);
97
98 int const GRID_LINES = 6;
99 int tile_width = (m_event_size.width() - GRID_LINES) / 7;
100 int width_remainder = (m_event_size.width() - GRID_LINES) % 7;
101 int y_offset = is_showing_days_of_the_week() ? 16 : 0;
102 y_offset += is_showing_month_and_year() ? 24 : 0;
103 int tile_height = (m_event_size.height() - y_offset - GRID_LINES) / 6;
104 int height_remainder = (m_event_size.height() - y_offset - GRID_LINES) % 6;
105
106 set_unadjusted_tile_size(tile_width, tile_height);
107 tile_width < 30 || tile_height < 30 ? set_grid(false) : set_grid(true);
108
109 for (int i = 0; i < 42; i++) {
110 m_tiles[0][i].width = tile_width;
111 m_tiles[0][i].height = tile_height;
112 }
113
114 for (auto& day : m_days)
115 day.width = tile_width;
116
117 for (int i = 0; i < width_remainder; i++) {
118 m_days[i].width = (tile_width + 1);
119 for (int j = i; j < i + 36; j += 7) {
120 m_tiles[0][j].width = tile_width + 1;
121 }
122 }
123
124 for (int j = 0; j < height_remainder * 7; j++)
125 m_tiles[0][j].height = tile_height + 1;
126
127 if (is_showing_days_of_the_week()) {
128 for (int i = 0; i < 7; i++) {
129 if (m_event_size.width() < 138)
130 m_days[i].name = micro_day_names[i];
131 else if (m_event_size.width() < 200)
132 m_days[i].name = mini_day_names[i];
133 else if (m_event_size.width() < 480)
134 m_days[i].name = short_day_names[i];
135 else
136 m_days[i].name = long_day_names[i];
137 }
138 }
139 } else {
140 if (m_event_size.width() < 140 && m_event_size.height() < 120)
141 set_show_year(false);
142 else if (m_event_size.width() >= 140 && m_event_size.height() >= 120)
143 set_show_year(true);
144
145 set_show_month_and_year(false);
146
147 int const VERT_GRID_LINES = 27;
148 int const HORI_GRID_LINES = 15;
149 int const THREADING = 3;
150 int const MONTH_TITLE = 19;
151 int tile_width = (m_event_size.width() - VERT_GRID_LINES) / 28;
152 int width_remainder = (m_event_size.width() - VERT_GRID_LINES) % 28;
153 int y_offset = is_showing_year() ? 22 : 0;
154 y_offset += (MONTH_TITLE * 3) + (THREADING * 3);
155 int tile_height = (m_event_size.height() - y_offset - HORI_GRID_LINES) / 18;
156 int height_remainder = (m_event_size.height() - y_offset - HORI_GRID_LINES) % 18;
157
158 set_grid(false);
159 set_unadjusted_tile_size(tile_width, tile_height);
160 if (unadjusted_tile_size().width() < 17 || unadjusted_tile_size().height() < 13)
161 m_show_month_tiles = true;
162 else
163 m_show_month_tiles = false;
164
165 if (m_show_month_tiles) {
166 int month_tile_width = m_event_size.width() / 4;
167 int width_remainder = m_event_size.width() % 4;
168 int y_offset = is_showing_year() ? 23 : 0;
169 int month_tile_height = (m_event_size.height() - y_offset) / 3;
170 int height_remainder = (m_event_size.height() - y_offset) % 3;
171
172 for (int i = 0; i < 12; i++) {
173 m_months[i].width = month_tile_width;
174 m_months[i].height = month_tile_height;
175 if (m_event_size.width() < 250)
176 m_months[i].name = short_month_names[i];
177 else
178 m_months[i].name = long_month_names[i];
179 }
180
181 if (width_remainder) {
182 for (int i = 0; i < width_remainder; i++) {
183 for (int j = i; j < 12; j += 4) {
184 m_months[j].width = month_tile_width + 1;
185 }
186 }
187 }
188
189 if (height_remainder) {
190 for (int i = 0; i < height_remainder * 4; i++) {
191 m_months[i].height = month_tile_height + 1;
192 }
193 }
194 return;
195 }
196
197 for (int i = 0; i < 12; i++) {
198 int remainder = 0;
199 if (i == 0 || i == 4 || i == 8)
200 remainder = min(width_remainder, 7);
201 if (i == 1 || i == 5 || i == 9)
202 width_remainder > 7 ? remainder = min(width_remainder - 7, 7) : remainder = 0;
203 if (i == 2 || i == 6 || i == 10)
204 width_remainder > 14 ? remainder = min(width_remainder - 14, 7) : remainder = 0;
205 if (i == 3 || i == 7 || i == 11)
206 width_remainder > 21 ? remainder = width_remainder - 21 : remainder = 0;
207 m_month_size[i].set_width(remainder + 6 + tile_width * 7);
208
209 if (i >= 0 && i <= 3)
210 remainder = min(height_remainder, 6);
211 if (i >= 4 && i <= 7)
212 height_remainder > 6 ? remainder = min(height_remainder - 6, 6) : remainder = 0;
213 if (i >= 8 && i <= 12)
214 height_remainder > 12 ? remainder = height_remainder - 12 : remainder = 0;
215 m_month_size[i].set_height(remainder + 5 + tile_height * 6);
216
217 for (int j = 0; j < 42; j++) {
218 m_tiles[i][j].width = tile_width;
219 m_tiles[i][j].height = tile_height;
220 }
221 }
222
223 if (width_remainder) {
224 for (int i = 0; i < 12; i += 4) {
225 for (int j = 0; j < min(width_remainder, 7); j++) {
226 for (int k = j; k < j + 36; k += 7) {
227 m_tiles[i][k].width = tile_width + 1;
228 }
229 }
230 }
231 }
232 if (width_remainder > 7) {
233 for (int i = 1; i < 12; i += 4) {
234 for (int j = 0; j < min(width_remainder - 7, 7); j++) {
235 for (int k = j; k < j + 36; k += 7) {
236 m_tiles[i][k].width = tile_width + 1;
237 }
238 }
239 }
240 }
241 if (width_remainder > 14) {
242 for (int i = 2; i < 12; i += 4) {
243 for (int j = 0; j < min(width_remainder - 14, 7); j++) {
244 for (int k = j; k < j + 36; k += 7) {
245 m_tiles[i][k].width = tile_width + 1;
246 }
247 }
248 }
249 }
250 if (width_remainder > 21) {
251 for (int i = 3; i < 12; i += 4) {
252 for (int j = 0; j < width_remainder - 21; j++) {
253 for (int k = j; k < j + 36; k += 7) {
254 m_tiles[i][k].width = tile_width + 1;
255 }
256 }
257 }
258 }
259 if (height_remainder) {
260 for (int i = 0; i < 4; i++) {
261 for (int j = 0; j < min(height_remainder, 6) * 7; j++) {
262 m_tiles[i][j].height = tile_height + 1;
263 }
264 }
265 }
266 if (height_remainder > 6) {
267 for (int i = 4; i < 8; i++) {
268 for (int j = 0; j < min(height_remainder - 6, 6) * 7; j++) {
269 m_tiles[i][j].height = tile_height + 1;
270 }
271 }
272 }
273 if (height_remainder > 12) {
274 for (int i = 8; i < 12; i++) {
275 for (int j = 0; j < (height_remainder - 12) * 7; j++) {
276 m_tiles[i][j].height = tile_height + 1;
277 }
278 }
279 }
280 }
281}
282
283void Calendar::update_tiles(unsigned view_year, unsigned view_month)
284{
285 set_view_date(view_year, view_month);
286
287 auto now = Core::DateTime::now();
288 unsigned months = mode() == Month ? 1 : 12;
289 for (unsigned i = 0; i < months; i++) {
290 if (mode() == Year)
291 view_month = i + 1;
292
293 auto first_day_of_current_month = Core::DateTime::create(view_year, view_month, 1);
294 unsigned start_of_month = (first_day_of_current_month.weekday() - to_underlying(m_first_day_of_week) + 7) % 7;
295 unsigned days_from_previous_month_to_show = start_of_month == 0 ? 7 : start_of_month;
296
297 for (unsigned j = 0; j < 42; j++) {
298 unsigned year;
299 unsigned month;
300 unsigned day;
301
302 if (j + 1 <= days_from_previous_month_to_show) {
303 // Day from previous month.
304 month = (view_month - 1 == 0) ? 12 : view_month - 1;
305 year = (month == 12) ? view_year - 1 : view_year;
306 day = days_in_month(year, month) + j + 1 - days_from_previous_month_to_show;
307 } else if (j + 1 > days_from_previous_month_to_show + first_day_of_current_month.days_in_month()) {
308 // Day from next month.
309 month = (view_month + 1) > 12 ? 1 : view_month + 1;
310 year = (month == 1) ? view_year + 1 : view_year;
311 day = j + 1 - days_from_previous_month_to_show - first_day_of_current_month.days_in_month();
312 } else {
313 // Day from current month.
314 month = view_month;
315 year = view_year;
316 day = j + 1 - days_from_previous_month_to_show;
317 }
318
319 m_tiles[i][j].year = year;
320 m_tiles[i][j].month = month;
321 m_tiles[i][j].day = day;
322 m_tiles[i][j].is_outside_selected_month = (month != view_month
323 || year != view_year);
324 m_tiles[i][j].is_selected = (year == m_selected_date.year()
325 && month == m_selected_date.month()
326 && day == m_selected_date.day()
327 && (mode() == Year ? !m_tiles[i][j].is_outside_selected_month : true));
328 m_tiles[i][j].is_today = (day == now.day()
329 && month == now.month()
330 && year == now.year());
331 }
332 }
333 update();
334}
335
336ErrorOr<String> Calendar::formatted_date(Format format)
337{
338 switch (format) {
339 case ShortMonthYear:
340 return String::formatted("{} {}", short_month_names[view_month() - 1], view_year());
341 case LongMonthYear:
342 return String::formatted("{} {}", long_month_names[view_month() - 1], view_year());
343 case MonthOnly:
344 return String::formatted("{}", long_month_names[view_month() - 1]);
345 case YearOnly:
346 return String::number(view_year());
347 }
348 VERIFY_NOT_REACHED();
349}
350
351void Calendar::paint_event(GUI::PaintEvent& event)
352{
353 GUI::Frame::paint_event(event);
354
355 GUI::Painter painter(*this);
356 painter.add_clip_rect(frame_inner_rect());
357 painter.add_clip_rect(event.rect());
358
359 if (has_grid())
360 painter.fill_rect(frame_inner_rect(), palette().threed_shadow2());
361 else
362 painter.fill_rect(frame_inner_rect(), palette().base());
363
364 painter.translate(frame_thickness(), frame_thickness());
365
366 int width = unadjusted_tile_size().width();
367 int height = unadjusted_tile_size().height();
368 int x_offset = 0;
369 int y_offset = 0;
370
371 if (is_showing_year()) {
372 auto year_only_rect = Gfx::IntRect(
373 0,
374 0,
375 frame_inner_rect().width(),
376 22);
377 y_offset += year_only_rect.height();
378 painter.fill_rect(year_only_rect, palette().hover_highlight());
379 painter.draw_text(year_only_rect, formatted_date(YearOnly).release_value_but_fixme_should_propagate_errors(), medium_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text());
380 painter.draw_line({ 0, y_offset }, { frame_inner_rect().width(), y_offset }, (!m_show_month_tiles ? palette().threed_shadow1() : palette().threed_shadow2()), 1);
381 y_offset += 1;
382 if (!m_show_month_tiles) {
383 painter.draw_line({ 0, y_offset }, { frame_inner_rect().width(), y_offset }, palette().threed_highlight(), 1);
384 y_offset += 1;
385 }
386 } else if (is_showing_month_and_year()) {
387 auto month_year_rect = Gfx::IntRect(
388 0,
389 0,
390 frame_inner_rect().width(),
391 22);
392 painter.fill_rect(month_year_rect, palette().hover_highlight());
393 month_year_rect.set_width(frame_inner_rect().width() / 2);
394 painter.draw_text(month_year_rect, formatted_date(MonthOnly).release_value_but_fixme_should_propagate_errors(), medium_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text());
395 month_year_rect.set_x(month_year_rect.width() + (frame_inner_rect().width() % 2 ? 1 : 0));
396 painter.draw_text(month_year_rect, formatted_date(YearOnly).release_value_but_fixme_should_propagate_errors(), medium_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text());
397 y_offset += 22;
398 painter.draw_line({ 0, y_offset }, { frame_inner_rect().width(), y_offset }, palette().threed_shadow1(), 1);
399 y_offset += 1;
400 painter.draw_line({ 0, y_offset }, { frame_inner_rect().width(), y_offset }, palette().threed_highlight(), 1);
401 y_offset += 1;
402 }
403
404 if (mode() == Year && m_show_month_tiles) {
405 int i = 0;
406 for (int j = 0; j < 3; j++) {
407 x_offset = 0;
408 for (int k = 0; k < 4; k++) {
409 if (k > 0)
410 x_offset += m_months[i - 1].width;
411 auto month_tile_rect = Gfx::IntRect(
412 x_offset,
413 y_offset,
414 m_months[i].width,
415 m_months[i].height);
416 m_months[i].rect = month_tile_rect.translated(frame_thickness(), frame_thickness());
417 Gfx::StylePainter::paint_button(
418 painter, month_tile_rect, palette(),
419 Gfx::ButtonStyle::Normal,
420 m_months[i].is_being_pressed,
421 m_months[i].is_hovered,
422 false, true, false);
423 set_font(small_font);
424 painter.draw_text(month_tile_rect, m_months[i].name, font(), Gfx::TextAlignment::Center, palette().base_text());
425 i++;
426 }
427 y_offset += m_months[i - 1].height;
428 }
429 return;
430 }
431
432 if (is_showing_days_of_the_week()) {
433 auto days_of_the_week_rect = Gfx::IntRect(
434 0,
435 y_offset,
436 frame_inner_rect().width(),
437 16);
438 painter.fill_rect(days_of_the_week_rect, palette().hover_highlight());
439 for (int i = 0; i < 7; i++) {
440 if (i > 0)
441 x_offset += m_days[i - 1].width + 1;
442 Gfx::IntRect day_rect = Gfx::IntRect(
443 x_offset,
444 y_offset,
445 m_days[i].width,
446 16);
447 auto const& day_name = m_days[(i + to_underlying(m_first_day_of_week)) % 7].name;
448 painter.draw_text(day_rect, day_name, small_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text());
449 }
450 y_offset += days_of_the_week_rect.height();
451 painter.draw_line({ 0, y_offset }, { frame_inner_rect().width(), y_offset }, palette().threed_shadow2(), 1);
452 y_offset += 1;
453 }
454
455 if (mode() == Month) {
456 int i = 0;
457 for (int j = 0; j < 6; j++) {
458 x_offset = 0;
459 if (j > 0)
460 y_offset += m_tiles[0][(j - 1) * 7].height + 1;
461 for (int k = 0; k < 7; k++) {
462 bool is_weekend = is_day_in_weekend((DayOfWeek)((k + to_underlying(m_first_day_of_week)) % 7));
463 if (k > 0)
464 x_offset += m_tiles[0][k - 1].width + 1;
465 auto tile_rect = Gfx::IntRect(
466 x_offset,
467 y_offset,
468 m_tiles[0][i].width,
469 m_tiles[0][i].height);
470 m_tiles[0][i].rect = tile_rect.translated(frame_thickness(), frame_thickness());
471
472 Color background_color = palette().base();
473
474 if (m_tiles[0][i].is_hovered || m_tiles[0][i].is_selected) {
475 background_color = palette().hover_highlight();
476 } else if (is_weekend) {
477 background_color = palette().gutter();
478 }
479
480 painter.fill_rect(tile_rect, background_color);
481
482 auto text_alignment = Gfx::TextAlignment::TopRight;
483 auto text_rect = Gfx::IntRect(
484 x_offset,
485 y_offset + 4,
486 m_tiles[0][i].width - 4,
487 font().pixel_size_rounded_up() + 4);
488
489 if (width > 150 && height > 150) {
490 set_font(extra_large_font);
491 } else if (width > 100 && height > 100) {
492 set_font(large_font);
493 } else if (width > 50 && height > 50) {
494 set_font(medium_font);
495 } else if (width >= 30 && height >= 30) {
496 set_font(small_font);
497 } else {
498 set_font(small_font);
499 text_alignment = Gfx::TextAlignment::Center;
500 text_rect = Gfx::IntRect(tile_rect);
501 }
502
503 auto display_date = DeprecatedString::number(m_tiles[0][i].day);
504 if (m_tiles[0][i].is_selected && (width < 30 || height < 30))
505 painter.draw_rect(tile_rect, palette().base_text());
506
507 if (m_tiles[0][i].is_today && !m_tiles[0][i].is_outside_selected_month) {
508 painter.draw_text(text_rect, display_date, font().bold_variant(), text_alignment, palette().base_text());
509 } else if (m_tiles[0][i].is_outside_selected_month) {
510 painter.draw_text(text_rect, display_date, m_tiles[0][i].is_today ? font().bold_variant() : font(), text_alignment, Color::LightGray);
511 } else {
512 painter.draw_text(text_rect, display_date, font(), text_alignment, palette().base_text());
513 }
514 i++;
515 }
516 }
517 } else {
518 for (int i = 0; i < 4; i++) {
519 static int x_month_offset;
520 x_month_offset += (i > 0 ? m_month_size[i - 1].width() + 1 : 0);
521 auto month_rect = Gfx::IntRect(
522 x_month_offset,
523 y_offset,
524 m_month_size[i].width(),
525 19);
526 painter.fill_rect(month_rect, palette().hover_highlight());
527 painter.draw_text(month_rect, long_month_names[i], medium_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text());
528 if (i > 0 && i < 4) {
529 painter.draw_line({ x_month_offset - 1, y_offset - 1 }, { x_month_offset - 1, y_offset + 18 }, palette().threed_shadow2(), 1);
530 painter.draw_line({ x_month_offset, y_offset - 1 }, { x_month_offset, y_offset + 18 }, palette().threed_highlight(), 1);
531 }
532 if (i == 3)
533 x_month_offset = 0;
534 }
535 y_offset += 19;
536 painter.draw_line({ 0, y_offset }, { frame_inner_rect().width(), y_offset }, palette().threed_shadow2(), 1);
537 y_offset += 1;
538
539 int x_translation = 0;
540 int y_translation = y_offset;
541 for (int l = 0; l < 12; l++) {
542 if ((l > 0 && l < 4) || (l > 4 && l < 8) || (l > 8)) {
543 x_translation += m_month_size[l - 1].width() + 1;
544 } else if (l % 4 == 0) {
545 x_translation = 0;
546 }
547 if (l < 4 || (l > 4 && l < 8) || l > 8) {
548 y_offset = y_translation;
549 } else if (l == 4 || l == 8) {
550 y_translation += m_month_size[l - 1].height();
551 painter.draw_line({ 0, y_translation }, { frame_inner_rect().width(), y_translation }, palette().threed_shadow1(), 1);
552 y_translation += 1;
553 painter.draw_line({ 0, y_translation }, { frame_inner_rect().width(), y_translation }, palette().threed_highlight(), 1);
554 y_translation += 1;
555 y_offset = y_translation;
556 for (int i = l; i < (l == 4 ? 8 : 12); i++) {
557 static int x_month_offset;
558 x_month_offset += (i > (l == 4 ? 4 : 8) ? m_month_size[i - 1].width() + 1 : 0);
559 auto month_rect = Gfx::IntRect(
560 x_month_offset,
561 y_offset,
562 m_month_size[i].width(),
563 19);
564 painter.fill_rect(month_rect, palette().hover_highlight());
565 painter.draw_text(month_rect, long_month_names[i], medium_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text());
566 if (i > (l == 4 ? 4 : 8) && i < (l == 4 ? 8 : 12)) {
567 painter.draw_line({ x_month_offset - 1, y_offset - 1 }, { x_month_offset - 1, y_offset + 18 }, palette().threed_shadow2(), 1);
568 painter.draw_line({ x_month_offset, y_offset - 1 }, { x_month_offset, y_offset + 18 }, palette().threed_highlight(), 1);
569 }
570 if (i == 7 || i == 11)
571 x_month_offset = 0;
572 }
573 y_translation += 19;
574 painter.draw_line({ 0, y_translation }, { frame_inner_rect().width(), y_translation }, palette().threed_shadow2(), 1);
575 y_translation += 1;
576 y_offset = y_translation;
577 }
578
579 int i = 0;
580 for (int j = 0; j < 6; j++) {
581 x_offset = 0;
582 if (j > 0)
583 y_offset += m_tiles[l][(j - 1) * 7].height + (j < 6 ? 1 : 0);
584 if (j == 0 && l != 3 && l != 7 && l != 11) {
585 painter.draw_line(
586 { m_month_size[l].width() + x_translation, y_offset },
587 { m_month_size[l].width() + x_translation, y_offset + m_month_size[l].height() },
588 palette().threed_shadow2(),
589 1);
590 }
591 for (int k = 0; k < 7; k++) {
592 if (k > 0)
593 x_offset += m_tiles[l][k - 1].width + 1;
594 auto tile_rect = Gfx::IntRect(
595 x_offset + x_translation,
596 y_offset,
597 m_tiles[l][i].width,
598 m_tiles[l][i].height);
599 m_tiles[l][i].rect = tile_rect.translated(frame_thickness(), frame_thickness());
600
601 if (m_tiles[l][i].is_hovered || m_tiles[l][i].is_selected)
602 painter.fill_rect(tile_rect, palette().hover_highlight());
603 else
604 painter.fill_rect(tile_rect, palette().base());
605
606 if (width > 50 && height > 50) {
607 set_font(medium_font);
608 } else {
609 set_font(small_font);
610 }
611
612 auto display_date = DeprecatedString::number(m_tiles[l][i].day);
613 if (m_tiles[l][i].is_selected)
614 painter.draw_rect(tile_rect, palette().base_text());
615
616 if (m_tiles[l][i].is_today && !m_tiles[l][i].is_outside_selected_month) {
617 painter.draw_text(tile_rect, display_date, font().bold_variant(), Gfx::TextAlignment::Center, palette().base_text());
618 } else if (!m_tiles[l][i].is_outside_selected_month) {
619 painter.draw_text(tile_rect, display_date, font(), Gfx::TextAlignment::Center, palette().base_text());
620 }
621 i++;
622 }
623 }
624 }
625 }
626}
627
628void Calendar::leave_event(Core::Event&)
629{
630 int months;
631 mode() == Month ? months = 1 : months = 12;
632 for (int i = 0; i < months; i++) {
633 if (mode() == Year && m_show_month_tiles) {
634 m_months[i].is_hovered = false;
635 continue;
636 } else {
637 for (int j = 0; j < 42; j++) {
638 m_tiles[i][j].is_hovered = false;
639 }
640 }
641 }
642 update();
643}
644
645void Calendar::mousemove_event(GUI::MouseEvent& event)
646{
647 static int last_index_i;
648 static int last_index_j;
649
650 if (mode() == Year && m_show_month_tiles) {
651 if (m_months[last_index_i].rect.contains(event.position()) && (m_months[last_index_i].is_hovered || m_months[last_index_i].is_being_pressed)) {
652 return;
653 } else {
654 m_months[last_index_i].is_hovered = false;
655 m_months[last_index_i].is_being_pressed = false;
656 update(m_months[last_index_i].rect);
657 }
658 } else {
659 if (m_tiles[last_index_i][last_index_j].rect.contains(event.position()) && m_tiles[last_index_i][last_index_j].is_hovered) {
660 return;
661 } else {
662 m_tiles[last_index_i][last_index_j].is_hovered = false;
663 update(m_tiles[last_index_i][last_index_j].rect);
664 }
665 }
666
667 int months;
668 mode() == Month ? months = 1 : months = 12;
669 for (int i = 0; i < months; i++) {
670 if (mode() == Year && m_show_month_tiles) {
671 if (m_months[i].rect.contains(event.position())) {
672 if (m_currently_pressed_index == -1 || m_currently_pressed_index == i)
673 m_months[i].is_hovered = true;
674 if (m_currently_pressed_index == i)
675 m_months[i].is_being_pressed = true;
676 update(m_months[last_index_i].rect);
677 if (m_months[i].is_being_pressed == true)
678 m_currently_pressed_index = i;
679 last_index_i = i;
680 update(m_months[i].rect);
681 break;
682 }
683 } else {
684 for (int j = 0; j < 42; j++) {
685 if (mode() == Year && m_tiles[i][j].is_outside_selected_month)
686 continue;
687 if (m_tiles[i][j].rect.contains(event.position())) {
688 m_tiles[i][j].is_hovered = true;
689 update(m_tiles[last_index_i][last_index_j].rect);
690 last_index_i = i;
691 last_index_j = j;
692 update(m_tiles[i][j].rect);
693 break;
694 }
695 }
696 }
697 }
698}
699
700void Calendar::mouseup_event(GUI::MouseEvent& event)
701{
702 int months;
703 mode() == Month ? months = 1 : months = 12;
704 for (int i = 0; i < months; i++) {
705 if (mode() == Year && m_show_month_tiles) {
706 if (m_months[i].rect.contains(event.position()) && m_months[i].is_being_pressed) {
707 set_view_date(view_year(), (unsigned)i + 1);
708 toggle_mode();
709 if (on_month_click)
710 on_month_click();
711 }
712 } else {
713 for (int j = 0; j < 42; j++) {
714 if (mode() == Year && m_tiles[i][j].is_outside_selected_month)
715 continue;
716 if (m_tiles[i][j].rect.contains(event.position())) {
717 m_previous_selected_date = m_selected_date;
718 m_selected_date = Core::DateTime::create(m_tiles[i][j].year, m_tiles[i][j].month, m_tiles[i][j].day);
719 update_tiles(m_selected_date.year(), m_selected_date.month());
720 if (on_tile_click)
721 on_tile_click();
722 }
723 }
724 }
725 if (months == 12) {
726 m_months[i].is_being_pressed = false;
727 m_months[i].is_hovered = false;
728 }
729 }
730 m_currently_pressed_index = -1;
731 update();
732}
733
734void Calendar::mousedown_event(GUI::MouseEvent& event)
735{
736 if (mode() == Year && m_show_month_tiles) {
737 for (int i = 0; i < 12; i++) {
738 if (m_months[i].rect.contains(event.position())) {
739 m_months[i].is_being_pressed = true;
740 m_currently_pressed_index = i;
741 update(m_months[i].rect);
742 break;
743 }
744 }
745 }
746}
747
748void Calendar::doubleclick_event(GUI::MouseEvent& event)
749{
750 int months;
751 mode() == Month ? months = 1 : months = 12;
752 for (int i = 0; i < months; i++) {
753 for (int j = 0; j < 42; j++) {
754 if (m_tiles[i][j].day != m_previous_selected_date.day())
755 continue;
756 if (mode() == Year && m_tiles[i][j].is_outside_selected_month)
757 continue;
758 if (m_tiles[i][j].rect.contains(event.position())) {
759 if (on_tile_doubleclick)
760 on_tile_doubleclick();
761 }
762 }
763 }
764}
765
766size_t Calendar::day_of_week_index(DeprecatedString const& day_name)
767{
768 auto const& day_names = AK::long_day_names;
769 return AK::find_index(day_names.begin(), day_names.end(), day_name);
770}
771
772void Calendar::config_string_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value)
773{
774 if (domain == "Calendar" && group == "View" && key == "FirstDayOfWeek") {
775 m_first_day_of_week = static_cast<DayOfWeek>(day_of_week_index(value));
776 update_tiles(m_view_year, m_view_month);
777 } else if (domain == "Calendar" && group == "View" && key == "FirstDayOfWeekend") {
778 m_first_day_of_weekend = static_cast<DayOfWeek>(day_of_week_index(value));
779 update();
780 }
781}
782
783void Calendar::config_i32_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, i32 value)
784{
785 if (domain == "Calendar" && group == "View" && key == "WeekendLength") {
786 m_weekend_length = value;
787 update();
788 }
789}
790
791bool Calendar::is_day_in_weekend(DayOfWeek day)
792{
793 auto day_index = to_underlying(day);
794 auto weekend_start_index = to_underlying(m_first_day_of_weekend);
795 auto weekend_end_index = weekend_start_index + m_weekend_length;
796
797 if (day_index < weekend_start_index)
798 day_index += 7;
799
800 return day_index < weekend_end_index;
801}
802
803}