Calendar.cpp 33 KB

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