Calendar.cpp 32 KB

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