diff --git a/Base/etc/FileIconProvider.ini b/Base/etc/FileIconProvider.ini index 4ec378fa82..12fd93d3a0 100644 --- a/Base/etc/FileIconProvider.ini +++ b/Base/etc/FileIconProvider.ini @@ -30,6 +30,7 @@ ruby=*.rb shell=*.sh,*.bash,*.zsh sound=*.wav,*.flac,*.mp3,*.qoa spreadsheet=*.sheets,*.csv +calendar=*.cal text=*.txt truetype=*.ttf pixelpaint=*.pp diff --git a/Base/res/apps/Calendar.af b/Base/res/apps/Calendar.af index 81c88b8149..81f1b1e871 100644 --- a/Base/res/apps/Calendar.af +++ b/Base/res/apps/Calendar.af @@ -2,3 +2,6 @@ Name=Calendar Executable=/bin/Calendar Category=Office + +[Launcher] +FileTypes=cal diff --git a/Base/res/icons/16x16/filetype-calendar.png b/Base/res/icons/16x16/filetype-calendar.png new file mode 100644 index 0000000000..90296ab2e5 Binary files /dev/null and b/Base/res/icons/16x16/filetype-calendar.png differ diff --git a/Base/res/icons/32x32/filetype-calendar.png b/Base/res/icons/32x32/filetype-calendar.png new file mode 100644 index 0000000000..54970b9c7f Binary files /dev/null and b/Base/res/icons/32x32/filetype-calendar.png differ diff --git a/Userland/Applications/Calendar/AddEventDialog.cpp b/Userland/Applications/Calendar/AddEventDialog.cpp index 299093a2e0..cec8890b67 100644 --- a/Userland/Applications/Calendar/AddEventDialog.cpp +++ b/Userland/Applications/Calendar/AddEventDialog.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2019-2020, Ryan Grieb - * Copyright (c) 2022, the SerenityOS developers. + * Copyright (c) 2022-2023, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ @@ -19,9 +19,12 @@ #include #include -AddEventDialog::AddEventDialog(Core::DateTime date_time, Window* parent_window) +namespace Calendar { + +AddEventDialog::AddEventDialog(Core::DateTime date_time, EventManager& event_manager, Window* parent_window) : Dialog(parent_window) , m_date_time(date_time) + , m_event_manager(event_manager) { resize(158, 130); set_title("Add Event"); @@ -42,6 +45,7 @@ AddEventDialog::AddEventDialog(Core::DateTime date_time, Window* parent_window) add_label.set_font(Gfx::FontDatabase::default_font().bold_variant()); auto& event_title_textbox = top_container.add(); + event_title_textbox.set_name("event_title_textbox"); event_title_textbox.set_fixed_height(20); auto& middle_container = widget->add(); @@ -92,8 +96,9 @@ AddEventDialog::AddEventDialog(Core::DateTime date_time, Window* parent_window) button_container.add_spacer().release_value_but_fixme_should_propagate_errors(); auto& ok_button = button_container.add("OK"_short_string); ok_button.set_fixed_size(80, 20); - ok_button.on_click = [this](auto) { - dbgln("TODO: Add event icon on specific tile"); + ok_button.on_click = [&](auto) { + add_event_to_calendar().release_value_but_fixme_should_propagate_errors(); + done(ExecResult::OK); }; @@ -110,6 +115,19 @@ AddEventDialog::AddEventDialog(Core::DateTime date_time, Window* parent_window) event_title_textbox.set_focus(true); } +ErrorOr AddEventDialog::add_event_to_calendar() +{ + JsonObject event; + auto start_date = TRY(String::formatted("{}-{:0>2d}-{:0>2d}", m_date_time.year(), m_date_time.month(), m_date_time.day())); + auto summary = find_descendant_of_type_named("event_title_textbox")->get_text(); + event.set("start_date", JsonValue(start_date)); + event.set("summary", JsonValue(summary)); + TRY(m_event_manager.add_event(event)); + m_event_manager.set_dirty(true); + + return {}; +} + int AddEventDialog::MonthListModel::row_count(const GUI::ModelIndex&) const { return 12; @@ -176,3 +194,5 @@ GUI::Variant AddEventDialog::MeridiemListModel::data(const GUI::ModelIndex& inde } return {}; } + +} diff --git a/Userland/Applications/Calendar/AddEventDialog.h b/Userland/Applications/Calendar/AddEventDialog.h index 5d7ad20de4..e7b0c532e2 100644 --- a/Userland/Applications/Calendar/AddEventDialog.h +++ b/Userland/Applications/Calendar/AddEventDialog.h @@ -1,30 +1,35 @@ /* * Copyright (c) 2019-2020, Ryan Grieb - * Copyright (c) 2022, the SerenityOS developers. + * Copyright (c) 2022-2023, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include "EventManager.h" #include #include #include #include +namespace Calendar { + class AddEventDialog final : public GUI::Dialog { C_OBJECT(AddEventDialog) public: virtual ~AddEventDialog() override = default; - static void show(Core::DateTime date_time, Window* parent_window = nullptr) + static void show(Core::DateTime date_time, EventManager& event_manager, Window* parent_window = nullptr) { - auto dialog = AddEventDialog::construct(date_time, parent_window); + auto dialog = AddEventDialog::construct(date_time, event_manager, parent_window); dialog->exec(); } private: - AddEventDialog(Core::DateTime date_time, Window* parent_window = nullptr); + AddEventDialog(Core::DateTime date_time, EventManager& event_manager, Window* parent_window = nullptr); + + ErrorOr add_event_to_calendar(); class MonthListModel final : public GUI::Model { public: @@ -65,4 +70,7 @@ private: }; Core::DateTime m_date_time; + EventManager& m_event_manager; }; + +} diff --git a/Userland/Applications/Calendar/CMakeLists.txt b/Userland/Applications/Calendar/CMakeLists.txt index 443b6faad2..7d22f4dff9 100644 --- a/Userland/Applications/Calendar/CMakeLists.txt +++ b/Userland/Applications/Calendar/CMakeLists.txt @@ -7,6 +7,9 @@ compile_gml(CalendarWindow.gml CalendarWindowGML.h calendar_window_gml) set(SOURCES AddEventDialog.cpp + CalendarWidget.cpp + EventCalendar.cpp + EventManager.cpp main.cpp ) @@ -15,4 +18,4 @@ set(GENERATED_SOURCES ) serenity_app(Calendar ICON app-calendar) -target_link_libraries(Calendar PRIVATE LibConfig LibCore LibGfx LibGUI LibMain) +target_link_libraries(Calendar PRIVATE LibConfig LibCore LibFileSystem LibFileSystemAccessClient LibGfx LibGUI LibMain) diff --git a/Userland/Applications/Calendar/CalendarWidget.cpp b/Userland/Applications/Calendar/CalendarWidget.cpp new file mode 100644 index 0000000000..46bb57b9a3 --- /dev/null +++ b/Userland/Applications/Calendar/CalendarWidget.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2023, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "CalendarWidget.h" +#include "AddEventDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Calendar { + +ErrorOr> CalendarWidget::create(GUI::Window* parent_window) +{ + auto widget = TRY(AK::adopt_nonnull_ref_or_enomem(new (nothrow) CalendarWidget)); + TRY(widget->load_from_gml(calendar_window_gml)); + + widget->m_event_calendar = widget->find_descendant_of_type_named("calendar"); + widget->create_on_events_change(); + + auto toolbar = widget->find_descendant_of_type_named("toolbar"); + auto calendar = widget->m_event_calendar; + + auto prev_date_action = TRY(widget->create_prev_date_action()); + auto next_date_action = TRY(widget->create_next_date_action()); + + auto add_event_action = TRY(widget->create_add_event_action()); + + auto jump_to_action = TRY(widget->create_jump_to_action()); + + auto view_month_action = TRY(widget->create_view_month_action()); + view_month_action->set_checked(true); + + auto view_year_action = TRY(widget->create_view_year_action()); + auto view_type_action_group = make(); + + view_type_action_group->set_exclusive(true); + view_type_action_group->add_action(*view_month_action); + view_type_action_group->add_action(*view_year_action); + auto default_view = Config::read_string("Calendar"sv, "View"sv, "DefaultView"sv, "Month"sv); + if (default_view == "Year") + view_year_action->set_checked(true); + + auto open_settings_action = TRY(widget->create_open_settings_action()); + + (void)TRY(toolbar->try_add_action(prev_date_action)); + (void)TRY(toolbar->try_add_action(next_date_action)); + TRY(toolbar->try_add_separator()); + (void)TRY(toolbar->try_add_action(jump_to_action)); + (void)TRY(toolbar->try_add_action(add_event_action)); + TRY(toolbar->try_add_separator()); + (void)TRY(toolbar->try_add_action(view_month_action)); + (void)TRY(toolbar->try_add_action(view_year_action)); + (void)TRY(toolbar->try_add_action(open_settings_action)); + + widget->create_on_tile_doubleclick(); + + calendar->on_month_click = [&] { + view_month_action->set_checked(true); + }; + + auto new_calendar_action = TRY(widget->create_new_calendar_action()); + auto open_calendar_action = widget->create_open_calendar_action(); + + auto save_as_action = widget->create_save_as_action(); + auto save_action = widget->create_save_action(save_as_action); + + auto& file_menu = parent_window->add_menu("&File"_short_string); + file_menu.add_action(open_settings_action); + file_menu.add_action(new_calendar_action); + file_menu.add_action(open_calendar_action); + file_menu.add_action(save_as_action); + file_menu.add_action(save_action); + + TRY(file_menu.try_add_separator()); + + TRY(file_menu.try_add_action(GUI::CommonActions::make_quit_action([](auto&) { + GUI::Application::the()->quit(); + }))); + + auto& event_menu = parent_window->add_menu("&Event"_short_string); + event_menu.add_action(add_event_action); + + auto view_menu = TRY(parent_window->try_add_menu("&View"_short_string)); + TRY(view_menu->try_add_action(*view_month_action)); + TRY(view_menu->try_add_action(*view_year_action)); + + auto help_menu = TRY(parent_window->try_add_menu("&Help"_short_string)); + TRY(help_menu->try_add_action(GUI::CommonActions::make_command_palette_action(parent_window))); + TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("Calendar", TRY(GUI::Icon::try_create_default_icon("app-calendar"sv)), parent_window))); + + return widget; +} + +void CalendarWidget::create_on_events_change() +{ + m_event_calendar->event_manager().on_events_change = [&]() { + m_event_calendar->repaint(); + window()->set_modified(true); + update_window_title(); + }; +} + +void CalendarWidget::load_file(FileSystemAccessClient::File file) +{ + auto result = m_event_calendar->event_manager().load_file(file); + if (result.is_error()) { + GUI::MessageBox::show_error(window(), DeprecatedString::formatted("Cannot load file: {}", result.error())); + return; + } + + window()->set_modified(false); + update_window_title(); +} + +NonnullRefPtr CalendarWidget::create_save_action(GUI::Action& save_as_action) +{ + return GUI::CommonActions::make_save_action([&](auto&) { + if (current_filename().is_empty()) { + save_as_action.activate(); + return; + } + + auto response = FileSystemAccessClient::Client::the().request_file(window(), current_filename().to_deprecated_string(), Core::File::OpenMode::Write); + if (response.is_error()) + return; + + auto result = m_event_calendar->event_manager().save(response.value()); + if (result.is_error()) { + GUI::MessageBox::show_error(window(), DeprecatedString::formatted("Cannot save file: {}", result.error())); + return; + } + + window()->set_modified(false); + update_window_title(); + }); +} + +NonnullRefPtr CalendarWidget::create_save_as_action() +{ + return GUI::CommonActions::make_save_as_action([&](auto&) { + auto response = FileSystemAccessClient::Client::the().save_file(window(), "calendar", "cal"); + if (response.is_error()) + return; + + auto result = m_event_calendar->event_manager().save(response.value()); + if (result.is_error()) { + GUI::MessageBox::show_error(window(), DeprecatedString::formatted("Cannot save file: {}", result.error())); + return; + } + + window()->set_modified(false); + update_window_title(); + }); +} + +ErrorOr> CalendarWidget::create_new_calendar_action() +{ + return GUI::Action::create("&New Calendar", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-calendar.png"sv)), [&](const GUI::Action&) { + auto response = FileSystemAccessClient::Client::the().save_file(window(), "calendar", "cal", Core::File::OpenMode::Write); + + if (response.is_error()) + return; + + m_event_calendar->event_manager().clear(); + + auto result = m_event_calendar->event_manager().save(response.value()); + if (result.is_error()) { + GUI::MessageBox::show_error(window(), DeprecatedString::formatted("Cannot save file: {}", result.error())); + return; + } + + update_window_title(); + }); +} + +NonnullRefPtr CalendarWidget::create_open_calendar_action() +{ + return GUI::CommonActions::make_open_action([&](auto&) { + auto response = FileSystemAccessClient::Client::the().open_file(window()); + if (response.is_error()) + return; + (void)load_file(response.release_value()); + }); +} + +ErrorOr> CalendarWidget::create_prev_date_action() +{ + return GUI::Action::create({}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"sv)), [&](const GUI::Action&) { + m_event_calendar->show_previous_date(); + }); +} + +ErrorOr> CalendarWidget::create_next_date_action() +{ + return GUI::Action::create({}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"sv)), [&](const GUI::Action&) { + m_event_calendar->show_next_date(); + }); +} + +void CalendarWidget::update_window_title() +{ + StringBuilder builder; + if (current_filename().is_empty()) + builder.append("Untitled"sv); + else + builder.append(current_filename()); + builder.append("[*] - Calendar"sv); + + window()->set_title(builder.to_deprecated_string()); +} + +ErrorOr> CalendarWidget::create_add_event_action() +{ + return GUI::Action::create("&Add Event", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/add-event.png"sv)), [&](const GUI::Action&) { + AddEventDialog::show(m_event_calendar->selected_date(), m_event_calendar->event_manager(), window()); + }); +} + +ErrorOr> CalendarWidget::create_jump_to_action() +{ + return GUI::Action::create("Jump to &Today", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/calendar-date.png"sv)), [&](const GUI::Action&) { + m_event_calendar->set_selected_date(Core::DateTime::now()); + m_event_calendar->update_tiles(Core::DateTime::now().year(), Core::DateTime::now().month()); + }); +} + +ErrorOr> CalendarWidget::create_view_month_action() +{ + return GUI::Action::create_checkable("&Month View", { Mod_Ctrl, KeyCode::Key_1 }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/calendar-month-view.png"sv)), [&](const GUI::Action&) { + if (m_event_calendar->mode() == GUI::Calendar::Year) + m_event_calendar->toggle_mode(); + }); +} + +ErrorOr> CalendarWidget::create_view_year_action() +{ + return GUI::Action::create_checkable("&Year View", { Mod_Ctrl, KeyCode::Key_2 }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/icon-view.png"sv)), [&](const GUI::Action&) { + if (m_event_calendar->mode() == GUI::Calendar::Month) + m_event_calendar->toggle_mode(); + }); +} + +ErrorOr> CalendarWidget::create_open_settings_action() +{ + return GUI::Action::create("Calendar &Settings", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-settings.png"sv)), [&](GUI::Action const&) { + GUI::Process::spawn_or_show_error(window(), "/bin/CalendarSettings"sv); + }); +} + +void CalendarWidget::create_on_tile_doubleclick() +{ + m_event_calendar->on_tile_doubleclick = [&] { + AddEventDialog::show(m_event_calendar->selected_date(), m_event_calendar->event_manager(), window()); + }; +} + +} diff --git a/Userland/Applications/Calendar/CalendarWidget.h b/Userland/Applications/Calendar/CalendarWidget.h new file mode 100644 index 0000000000..4659928cea --- /dev/null +++ b/Userland/Applications/Calendar/CalendarWidget.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "EventCalendar.h" +#include +#include +#include +#include + +namespace Calendar { + +class CalendarWidget final : public GUI::Widget { + C_OBJECT(CalendarWidget); + +public: + static ErrorOr> create(GUI::Window*); + virtual ~CalendarWidget() override = default; + + void update_window_title(); + void load_file(FileSystemAccessClient::File file); + +private: + void create_on_tile_doubleclick(); + + String const& current_filename() const { return m_event_calendar->event_manager().current_filename(); } + + void create_on_events_change(); + NonnullRefPtr create_save_as_action(); + NonnullRefPtr create_save_action(GUI::Action& save_as_action); + ErrorOr> create_new_calendar_action(); + NonnullRefPtr create_open_calendar_action(); + ErrorOr> create_prev_date_action(); + ErrorOr> create_next_date_action(); + ErrorOr> create_add_event_action(); + ErrorOr> create_jump_to_action(); + ErrorOr> create_view_month_action(); + ErrorOr> create_view_year_action(); + ErrorOr> create_open_settings_action(); + + RefPtr m_event_calendar; +}; + +} diff --git a/Userland/Applications/Calendar/CalendarWindow.gml b/Userland/Applications/Calendar/CalendarWindow.gml index 23825165d0..1989ebfb14 100644 --- a/Userland/Applications/Calendar/CalendarWindow.gml +++ b/Userland/Applications/Calendar/CalendarWindow.gml @@ -10,7 +10,7 @@ } } - @GUI::Calendar { + @::Calendar::EventCalendar { name: "calendar" } } diff --git a/Userland/Applications/Calendar/EventCalendar.cpp b/Userland/Applications/Calendar/EventCalendar.cpp new file mode 100644 index 0000000000..7188c2a8f8 --- /dev/null +++ b/Userland/Applications/Calendar/EventCalendar.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "EventCalendar.h" +#include +#include +#include + +REGISTER_WIDGET(::Calendar, EventCalendar); + +namespace Calendar { + +static constexpr int tile_breakpoint = 50; + +EventCalendar::EventCalendar(Core::DateTime date_time, Mode mode) + : Calendar(date_time, mode) + , m_event_manager(EventManager::create()) +{ +} + +void EventCalendar::paint_tile(GUI::Painter& painter, GUI::Calendar::Tile& tile, Gfx::IntRect& tile_rect, int x_offset, int y_offset, int day_offset) +{ + Calendar::paint_tile(painter, tile, tile_rect, x_offset, y_offset, day_offset); + + auto events = m_event_manager->events(); + + if (tile.width > tile_breakpoint && tile.height > tile_breakpoint) { + auto index = 0; + auto font_height = font().x_height(); + events.for_each([&](JsonValue const& value) { + auto const& event = value.as_object(); + + if (!event.has("start_date"sv) || !event.has("summary"sv)) + return; + + auto start_date = event.get("start_date"sv).value().to_deprecated_string(); + auto summary = event.get("summary"sv).value().to_deprecated_string(); + + if (start_date == DeprecatedString::formatted("{}-{:0>2d}-{:0>2d}", tile.year, tile.month, tile.day)) { + + auto text_rect = tile.rect.translated(4, 4 + (font_height + 8) * ++index); + + painter.draw_text(text_rect, summary, Gfx::FontDatabase::default_font(), Gfx::TextAlignment::TopLeft, palette().base_text()); + } + }); + } +} + +} diff --git a/Userland/Applications/Calendar/EventCalendar.h b/Userland/Applications/Calendar/EventCalendar.h new file mode 100644 index 0000000000..dd97216691 --- /dev/null +++ b/Userland/Applications/Calendar/EventCalendar.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "EventManager.h" +#include +#include + +namespace Calendar { + +class EventCalendar final : public GUI::Calendar { + C_OBJECT(EventCalendar); + +public: + virtual ~EventCalendar() override = default; + + EventManager& event_manager() const { return *m_event_manager; } + +private: + EventCalendar(Core::DateTime date_time = Core::DateTime::now(), Mode mode = Month); + + ErrorOr save(FileSystemAccessClient::File& file); + ErrorOr load_file(FileSystemAccessClient::File& file); + + virtual void paint_tile(GUI::Painter&, GUI::Calendar::Tile&, Gfx::IntRect&, int x_offset, int y_offset, int day_offset) override; + + OwnPtr m_event_manager; +}; + +} diff --git a/Userland/Applications/Calendar/EventManager.cpp b/Userland/Applications/Calendar/EventManager.cpp new file mode 100644 index 0000000000..28c3e0e286 --- /dev/null +++ b/Userland/Applications/Calendar/EventManager.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "EventManager.h" +#include +#include +#include + +namespace Calendar { + +EventManager::EventManager() +{ +} + +OwnPtr EventManager::create() +{ + return adopt_own(*new EventManager()); +} + +ErrorOr EventManager::add_event(JsonObject event) +{ + TRY(m_events.append(move(event))); + set_dirty(true); + on_events_change(); + + return {}; +} + +void EventManager::set_events(JsonArray events) +{ + m_events = move(events); + on_events_change(); +} + +ErrorOr EventManager::save(FileSystemAccessClient::File& file) +{ + set_filename(file.filename()); + set_dirty(false); + + auto stream = file.release_stream(); + TRY(stream->write_some(m_events.to_deprecated_string().bytes())); + stream->close(); + + return {}; +} + +ErrorOr EventManager::load_file(FileSystemAccessClient::File& file) +{ + set_filename(file.filename()); + set_dirty(false); + + auto content = TRY(file.stream().read_until_eof()); + auto events = TRY(AK::JsonParser(content).parse()); + + set_events(events.as_array()); + + return {}; +} + +} diff --git a/Userland/Applications/Calendar/EventManager.h b/Userland/Applications/Calendar/EventManager.h new file mode 100644 index 0000000000..2db3cd01d8 --- /dev/null +++ b/Userland/Applications/Calendar/EventManager.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Calendar { + +class EventManager { + AK_MAKE_NONCOPYABLE(EventManager); + AK_MAKE_NONMOVABLE(EventManager); + +public: + static OwnPtr create(); + + String const& current_filename() const { return m_current_filename; } + void set_filename(String const& filename) { m_current_filename = filename; } + bool dirty() const { return m_dirty; } + void set_dirty(bool dirty) { m_dirty = dirty; } + + ErrorOr save(FileSystemAccessClient::File& file); + ErrorOr load_file(FileSystemAccessClient::File& file); + ErrorOr add_event(JsonObject); + void set_events(JsonArray events); + void clear() { m_events.clear(); } + + JsonArray const& events() const { return m_events; } + + Function on_events_change; + +private: + explicit EventManager(); + + JsonArray m_events; + + String m_current_filename; + bool m_dirty { false }; +}; + +} diff --git a/Userland/Applications/Calendar/main.cpp b/Userland/Applications/Calendar/main.cpp index 28cf1023a9..9c838cd2f9 100644 --- a/Userland/Applications/Calendar/main.cpp +++ b/Userland/Applications/Calendar/main.cpp @@ -5,9 +5,12 @@ */ #include "AddEventDialog.h" -#include +#include "CalendarWidget.h" #include +#include #include +#include +#include #include #include #include @@ -16,24 +19,42 @@ #include #include #include +#include #include #include #include +#include +#include #include ErrorOr serenity_main(Main::Arguments arguments) { - TRY(Core::System::pledge("stdio recvfd sendfd rpath proc exec unix")); + TRY(Core::System::pledge("stdio recvfd sendfd rpath wpath cpath proc exec unix")); auto app = TRY(GUI::Application::create(arguments)); + StringView filename; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(filename, "File to read from", "file", Core::ArgsParser::Required::No); + + args_parser.parse(arguments); + + if (!filename.is_empty()) { + if (!FileSystem::exists(filename) || FileSystem::is_directory(filename)) { + warnln("File does not exist or is a directory: {}", filename); + return 1; + } + } + Config::pledge_domain("Calendar"); Config::monitor_domain("Calendar"); - TRY(Core::System::pledge("stdio recvfd sendfd rpath proc exec")); + TRY(Core::System::pledge("stdio recvfd sendfd rpath wpath cpath proc exec unix")); TRY(Core::System::unveil("/etc/timezone", "r")); TRY(Core::System::unveil("/res", "r")); TRY(Core::System::unveil("/bin/CalendarSettings", "x")); + TRY(Core::System::unveil("/tmp/session/%sid/portal/filesystemaccess", "rw")); TRY(Core::System::unveil(nullptr, nullptr)); auto app_icon = TRY(GUI::Icon::try_create_default_icon("app-calendar"sv)); @@ -42,92 +63,18 @@ ErrorOr serenity_main(Main::Arguments arguments) window->resize(600, 480); window->set_icon(app_icon.bitmap_for_size(16)); - auto main_widget = TRY(window->set_main_widget()); - TRY(main_widget->load_from_gml(calendar_window_gml)); - - auto toolbar = main_widget->find_descendant_of_type_named("toolbar"); - auto calendar = main_widget->find_descendant_of_type_named("calendar"); - - auto prev_date_action = GUI::Action::create({}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-back.png"sv)), [&](const GUI::Action&) { - calendar->show_previous_date(); - }); - - auto next_date_action = GUI::Action::create({}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/go-forward.png"sv)), [&](const GUI::Action&) { - calendar->show_next_date(); - }); - - auto add_event_action = GUI::Action::create("&Add Event", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/add-event.png"sv)), [&](const GUI::Action&) { - AddEventDialog::show(calendar->selected_date(), window); - }); - - auto jump_to_action = GUI::Action::create("Jump to &Today", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/calendar-date.png"sv)), [&](const GUI::Action&) { - calendar->set_selected_date(Core::DateTime::now()); - calendar->update_tiles(Core::DateTime::now().year(), Core::DateTime::now().month()); - }); - - auto view_month_action = GUI::Action::create_checkable("&Month View", { Mod_Ctrl, KeyCode::Key_1 }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/calendar-month-view.png"sv)), [&](const GUI::Action&) { - if (calendar->mode() == GUI::Calendar::Year) - calendar->toggle_mode(); - }); - view_month_action->set_checked(true); - - auto view_year_action = GUI::Action::create_checkable("&Year View", { Mod_Ctrl, KeyCode::Key_2 }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/icon-view.png"sv)), [&](const GUI::Action&) { - if (calendar->mode() == GUI::Calendar::Month) - calendar->toggle_mode(); - }); - - auto view_type_action_group = make(); - view_type_action_group->set_exclusive(true); - view_type_action_group->add_action(*view_month_action); - view_type_action_group->add_action(*view_year_action); - auto default_view = Config::read_string("Calendar"sv, "View"sv, "DefaultView"sv, "Month"sv); - if (default_view == "Year") - view_year_action->set_checked(true); - - auto open_settings_action = GUI::Action::create("Calendar &Settings", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-settings.png"sv)), [&](GUI::Action const&) { - GUI::Process::spawn_or_show_error(window, "/bin/CalendarSettings"sv); - }); - - (void)TRY(toolbar->try_add_action(prev_date_action)); - (void)TRY(toolbar->try_add_action(next_date_action)); - TRY(toolbar->try_add_separator()); - (void)TRY(toolbar->try_add_action(jump_to_action)); - (void)TRY(toolbar->try_add_action(add_event_action)); - TRY(toolbar->try_add_separator()); - (void)TRY(toolbar->try_add_action(view_month_action)); - (void)TRY(toolbar->try_add_action(view_year_action)); - (void)TRY(toolbar->try_add_action(open_settings_action)); - - calendar->on_tile_doubleclick = [&] { - AddEventDialog::show(calendar->selected_date(), window); - }; - - calendar->on_month_click = [&] { - view_month_action->set_checked(true); - }; - - auto& file_menu = window->add_menu("&File"_short_string); - file_menu.add_action(GUI::Action::create("&Add Event", { Mod_Ctrl | Mod_Shift, Key_E }, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/add-event.png"sv)), - [&](const GUI::Action&) { - AddEventDialog::show(calendar->selected_date(), window); - })); - file_menu.add_action(open_settings_action); - - TRY(file_menu.try_add_separator()); - - TRY(file_menu.try_add_action(GUI::CommonActions::make_quit_action([](auto&) { - GUI::Application::the()->quit(); - }))); - - auto view_menu = TRY(window->try_add_menu("&View"_short_string)); - TRY(view_menu->try_add_action(*view_month_action)); - TRY(view_menu->try_add_action(*view_year_action)); - - auto help_menu = TRY(window->try_add_menu("&Help"_short_string)); - TRY(help_menu->try_add_action(GUI::CommonActions::make_command_palette_action(window))); - TRY(help_menu->try_add_action(GUI::CommonActions::make_about_action("Calendar", app_icon, window))); + auto calendar_widget = TRY(Calendar::CalendarWidget::create(window)); + window->set_main_widget(calendar_widget); window->show(); + + if (!filename.is_empty()) { + auto response = FileSystemAccessClient::Client::the().request_file_read_only_approved(window, filename); + if (!response.is_error()) { + calendar_widget->load_file(response.release_value()); + } + } + app->exec(); return 0; diff --git a/Userland/Libraries/LibGUI/Calendar.cpp b/Userland/Libraries/LibGUI/Calendar.cpp index f2889a0ceb..7d9c34c9cc 100644 --- a/Userland/Libraries/LibGUI/Calendar.cpp +++ b/Userland/Libraries/LibGUI/Calendar.cpp @@ -396,8 +396,6 @@ void Calendar::paint_event(GUI::PaintEvent& event) painter.translate(frame_thickness(), frame_thickness()); - int width = unadjusted_tile_size().width(); - int height = unadjusted_tile_size().height(); int x_offset = 0; int y_offset = 0; @@ -492,7 +490,6 @@ void Calendar::paint_event(GUI::PaintEvent& event) if (j > 0) y_offset += m_tiles[0][(j - 1) * 7].height + 1; for (int k = 0; k < 7; k++) { - bool is_weekend = is_day_in_weekend((DayOfWeek)((k + to_underlying(m_first_day_of_week)) % 7)); if (k > 0) x_offset += m_tiles[0][k - 1].width + 1; auto tile_rect = Gfx::IntRect( @@ -502,48 +499,8 @@ void Calendar::paint_event(GUI::PaintEvent& event) m_tiles[0][i].height); m_tiles[0][i].rect = tile_rect.translated(frame_thickness(), frame_thickness()); - Color background_color = palette().base(); + paint_tile(painter, m_tiles[0][i], tile_rect, x_offset, y_offset, k); - if (m_tiles[0][i].is_hovered || m_tiles[0][i].is_selected) { - background_color = palette().hover_highlight(); - } else if (is_weekend) { - background_color = palette().gutter(); - } - - painter.fill_rect(tile_rect, background_color); - - auto text_alignment = Gfx::TextAlignment::TopRight; - auto text_rect = Gfx::IntRect( - x_offset, - y_offset + 4, - m_tiles[0][i].width - 4, - font().pixel_size_rounded_up() + 4); - - if (width > 150 && height > 150) { - set_font(extra_large_font); - } else if (width > 100 && height > 100) { - set_font(large_font); - } else if (width > 50 && height > 50) { - set_font(medium_font); - } else if (width >= 30 && height >= 30) { - set_font(small_font); - } else { - set_font(small_font); - text_alignment = Gfx::TextAlignment::Center; - text_rect = Gfx::IntRect(tile_rect); - } - - auto display_date = DeprecatedString::number(m_tiles[0][i].day); - if (m_tiles[0][i].is_selected && (width < 30 || height < 30)) - painter.draw_rect(tile_rect, palette().base_text()); - - if (m_tiles[0][i].is_today && !m_tiles[0][i].is_outside_selected_month) { - painter.draw_text(text_rect, display_date, font().bold_variant(), text_alignment, palette().base_text()); - } else if (m_tiles[0][i].is_outside_selected_month) { - painter.draw_text(text_rect, display_date, m_tiles[0][i].is_today ? font().bold_variant() : font(), text_alignment, Color::LightGray); - } else { - painter.draw_text(text_rect, display_date, font(), text_alignment, palette().base_text()); - } i++; } } @@ -631,26 +588,8 @@ void Calendar::paint_event(GUI::PaintEvent& event) m_tiles[l][i].height); m_tiles[l][i].rect = tile_rect.translated(frame_thickness(), frame_thickness()); - if (m_tiles[l][i].is_hovered || m_tiles[l][i].is_selected) - painter.fill_rect(tile_rect, palette().hover_highlight()); - else - painter.fill_rect(tile_rect, palette().base()); + paint_tile(painter, m_tiles[0][i], tile_rect, x_offset, y_offset, k); - if (width > 50 && height > 50) { - set_font(medium_font); - } else { - set_font(small_font); - } - - auto display_date = DeprecatedString::number(m_tiles[l][i].day); - if (m_tiles[l][i].is_selected) - painter.draw_rect(tile_rect, palette().base_text()); - - if (m_tiles[l][i].is_today && !m_tiles[l][i].is_outside_selected_month) { - painter.draw_text(tile_rect, display_date, font().bold_variant(), Gfx::TextAlignment::Center, palette().base_text()); - } else if (!m_tiles[l][i].is_outside_selected_month) { - painter.draw_text(tile_rect, display_date, font(), Gfx::TextAlignment::Center, palette().base_text()); - } i++; } } @@ -658,6 +597,80 @@ void Calendar::paint_event(GUI::PaintEvent& event) } } +void Calendar::paint_tile(GUI::Painter& painter, GUI::Calendar::Tile& tile, Gfx::IntRect& tile_rect, int x_offset, int y_offset, int day_offset) +{ + int width = unadjusted_tile_size().width(); + int height = unadjusted_tile_size().height(); + + if (mode() == Month) { + bool is_weekend = is_day_in_weekend((DayOfWeek)((day_offset + to_underlying(m_first_day_of_week)) % 7)); + + Color background_color = palette().base(); + + if (tile.is_hovered || tile.is_selected) { + background_color = palette().hover_highlight(); + } else if (is_weekend) { + background_color = palette().gutter(); + } + + painter.fill_rect(tile_rect, background_color); + + auto text_alignment = Gfx::TextAlignment::TopRight; + auto text_rect = Gfx::IntRect( + x_offset, + y_offset + 4, + tile.width - 4, + font().pixel_size_rounded_up() + 4); + + if (width > 150 && height > 150) { + set_font(extra_large_font); + } else if (width > 100 && height > 100) { + set_font(large_font); + } else if (width > 50 && height > 50) { + set_font(medium_font); + } else if (width >= 30 && height >= 30) { + set_font(small_font); + } else { + set_font(small_font); + text_alignment = Gfx::TextAlignment::Center; + text_rect = Gfx::IntRect(tile_rect); + } + + auto display_date = DeprecatedString::number(tile.day); + if (tile.is_selected && (width < 30 || height < 30)) + painter.draw_rect(tile_rect, palette().base_text()); + + if (tile.is_today && !tile.is_outside_selected_month) { + painter.draw_text(text_rect, display_date, font().bold_variant(), text_alignment, palette().base_text()); + } else if (tile.is_outside_selected_month) { + painter.draw_text(text_rect, display_date, tile.is_today ? font().bold_variant() : font(), text_alignment, Color::LightGray); + } else { + painter.draw_text(text_rect, display_date, font(), text_alignment, palette().base_text()); + } + } else { + if (tile.is_hovered || tile.is_selected) + painter.fill_rect(tile_rect, palette().hover_highlight()); + else + painter.fill_rect(tile_rect, palette().base()); + + if (width > 50 && height > 50) { + set_font(medium_font); + } else { + set_font(small_font); + } + + auto display_date = DeprecatedString::number(tile.day); + if (tile.is_selected) + painter.draw_rect(tile_rect, palette().base_text()); + + if (tile.is_today && !tile.is_outside_selected_month) { + painter.draw_text(tile_rect, display_date, font().bold_variant(), Gfx::TextAlignment::Center, palette().base_text()); + } else if (!tile.is_outside_selected_month) { + painter.draw_text(tile_rect, display_date, font(), Gfx::TextAlignment::Center, palette().base_text()); + } + } +} + void Calendar::leave_event(Core::Event&) { int months; diff --git a/Userland/Libraries/LibGUI/Calendar.h b/Userland/Libraries/LibGUI/Calendar.h index f46d2a91e0..a95d919665 100644 --- a/Userland/Libraries/LibGUI/Calendar.h +++ b/Userland/Libraries/LibGUI/Calendar.h @@ -17,12 +17,25 @@ namespace GUI { -class Calendar final +class Calendar : public GUI::AbstractScrollableWidget , public Config::Listener { C_OBJECT(Calendar) public: + struct Tile { + unsigned year; + unsigned month; + unsigned day; + Gfx::IntRect rect; + int width { 0 }; + int height { 0 }; + bool is_today { false }; + bool is_selected { false }; + bool is_hovered { false }; + bool is_outside_selected_month { false }; + }; + enum Mode { Month, Year @@ -35,6 +48,8 @@ public: YearOnly }; + virtual ~Calendar() override = default; + void set_selected_date(Core::DateTime date_time) { m_selected_date = date_time; } Core::DateTime selected_date() const { return m_selected_date; } @@ -77,20 +92,21 @@ public: virtual void config_string_did_change(StringView, StringView, StringView, StringView) override; virtual void config_i32_did_change(StringView, StringView, StringView, i32 value) override; + virtual void paint_event(GUI::PaintEvent&) override; + virtual void paint_tile(GUI::Painter&, GUI::Calendar::Tile&, Gfx::IntRect&, int x_offset, int y_offset, int day_offset); Function on_scroll; Function on_tile_click; Function on_tile_doubleclick; Function on_month_click; -private: +protected: Calendar(Core::DateTime date_time = Core::DateTime::now(), Mode mode = Month); - virtual ~Calendar() override = default; +private: static size_t day_of_week_index(DeprecatedString const&); virtual void resize_event(GUI::ResizeEvent&) override; - virtual void paint_event(GUI::PaintEvent&) override; virtual void mousemove_event(GUI::MouseEvent&) override; virtual void mousedown_event(MouseEvent&) override; virtual void mouseup_event(MouseEvent&) override; @@ -127,18 +143,6 @@ private: }; Vector m_months; - struct Tile { - unsigned year; - unsigned month; - unsigned day; - Gfx::IntRect rect; - int width { 0 }; - int height { 0 }; - bool is_today { false }; - bool is_selected { false }; - bool is_hovered { false }; - bool is_outside_selected_month { false }; - }; Vector m_tiles[12]; bool m_grid { true };