#include #include #include #include #include #include #include "inter.h" #include "inter-bold.h" extern bool should_exit; extern bool show_demo_window; //standard year static int year; static int current_year; static int current_month; static int current_day; //fonts static ImFont *inter_regular; static ImFont *inter_bold; char *open_file_dialog(); char *save_file_dialog(); void set_window_title(const char *title); int get_current_year(){ time_t t = time(NULL); struct tm tm = *localtime(&t); current_year = tm.tm_year + 1900; current_month = tm.tm_mon + 1; current_day = tm.tm_mday; // tm_year is years since 1900 return year = current_year; } bool is_leap_year(int year) { return ((year % 4) == 0 && (year % 100) != 0) || year % 400 == 0; } int weekday_from_day(int year, int month, int day) { int a = (14 - month) / 12; int y = year - a; int m = month + (12 * a) - 2; return (day + y + (y / 4) - (y / 100) + (y / 400) + ((31 * m) / 12) - 1) % 7; } int calendar_week_from_day(int year, int month, int day){ int days_sum[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; int days = days_sum[month - 1]; if (is_leap_year(year) && month > 2) days++; return (10 + days + day - weekday_from_day(year, month, day)) / 7; } int days_per_month(int year, int month){ int days_in_month[] = { 31,28,31,30,31,30,31,31,30,31,30,31 }; if (month == 2) { return is_leap_year(year) ? 29 : 28; } return days_in_month[month - 1]; } #define HYDROGEN_CONTEXT "WorkCalS" #define HYDROGEN_OPSLIMIT 10000 #define HYDROGEN_MEMLIMIT 0 #define HYDROGEN_THREADS 1 static uint8_t master_key[hydro_pwhash_MASTERKEYBYTES] = { 0x6c, 0x2e, 0xed, 0x47, 0x36, 0x29, 0xda, 0x11, 0x6e, 0xf4, 0x41, 0x66, 0x3b, 0xd7, 0xfa, 0x72, 0xf7, 0x51, 0x48, 0x6d, 0x10, 0x7b, 0xa5, 0x04, 0x00, 0x11, 0x2e, 0xc4, 0xf2, 0xdb, 0x77, 0x51 }; static uint8_t derived_key[hydro_secretbox_KEYBYTES]; static char password_input_buffer[256]; static char password_confirmation_input_buffer[256]; void init() { hydro_init(); ImGuiIO &io = ImGui::GetIO(); inter_regular = io.Fonts->AddFontFromMemoryCompressedTTF(inter_compressed_data, inter_compressed_size); inter_bold = io.Fonts->AddFontFromMemoryCompressedTTF(inter_bold_compressed_data, inter_bold_compressed_size); year = get_current_year(); } static char *save_file_path = NULL; static const char *wiki_url = "https://gitea.ammerhai.com/Ammerhai/work-calendar/wiki"; static const char *month_names[] = {"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"}; typedef struct Category { bool editing; ImColor color; char name[64] = "New Category"; } Category; static uint32_t num_categories = 0; static Category categories[128]; static bool legend_visible = true; static int selected_category = -1; typedef struct Categorized_Day { int year; int month; int day; int category; } Categorized_Day; static int year_min_size = 1; static uint32_t num_categorized_days = 0; static Categorized_Day categorized_days[365*50]; //Colors static ImVec4 table_white_bg = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); static ImVec4 table_saturday_bg = ImVec4(0.0f, 0.0f, 0.0f, 0.1f); static ImVec4 table_sunday_bg = ImVec4(0.0f, 0.0f, 0.0f, 0.2f); static ImVec4 table_hover_color = ImVec4(0.0f, 0.3f, 0.6f, 0.2f); static char plaintext_buffer[2 * 1024 * 1024] = {}; static char encrypted_buffer[hydro_secretbox_HEADERBYTES + sizeof(plaintext_buffer)]; void save(){ if (!save_file_path) return; uint32_t version = 1; #define write_to_buffer(x) memcpy(plaintext_buffer + offset, &(x), sizeof(x)); offset += sizeof(x); size_t offset = 0; write_to_buffer(version); write_to_buffer(num_categories); write_to_buffer(num_categorized_days); for (int i = 0; i < num_categories; i++) { write_to_buffer(categories[i].color); write_to_buffer(categories[i].name); } for (int i = 0; i < num_categorized_days; i++) { write_to_buffer(categorized_days[i].year); write_to_buffer(categorized_days[i].month); write_to_buffer(categorized_days[i].day); write_to_buffer(categorized_days[i].category); } #undef write_to_buffer if (hydro_secretbox_encrypt((uint8_t *)encrypted_buffer, plaintext_buffer, offset, 0, HYDROGEN_CONTEXT, derived_key) != 0) { return; //TODO error message } hydro_memzero(plaintext_buffer, sizeof(plaintext_buffer)); FILE *save_file = fopen(save_file_path, "wb"); fwrite(encrypted_buffer, hydro_secretbox_HEADERBYTES + offset, 1, save_file); fclose (save_file); hydro_memzero(encrypted_buffer, sizeof(encrypted_buffer)); char *title = (char *)calloc(1, strlen("Work Calendar - ") + strlen(save_file_path) + 1); memcpy(title, "Work Calendar - ", strlen("Work Calendar - ")); memcpy(title + strlen("Work Calendar - "), save_file_path, strlen(save_file_path)); set_window_title(title); free(title); } void load(){ FILE *save_file = fopen(save_file_path, "rb"); size_t num_bytes = fread(encrypted_buffer, 1, sizeof(encrypted_buffer), save_file); fclose (save_file); if ( num_bytes <= 0) { return; //TODO Popup warning } if (hydro_secretbox_decrypt(plaintext_buffer, (uint8_t *)encrypted_buffer, num_bytes, 0, HYDROGEN_CONTEXT, derived_key) != 0){ return; //TODO Popup warning } hydro_memzero(encrypted_buffer, sizeof(encrypted_buffer)); #define read_from_buffer(x) memcpy(&(x), plaintext_buffer + offset, sizeof(x)); offset += sizeof(x); size_t offset = 0; uint32_t version = 0; read_from_buffer(version); if (version != 1) { return; //TODO Popup warning } read_from_buffer(num_categories); read_from_buffer(num_categorized_days); if (num_categories > IM_ARRAYSIZE(categories) || num_categorized_days > IM_ARRAYSIZE(categorized_days)){ return; //TODO Popup warning } for (int i = 0; i < num_categories; i++) { read_from_buffer(categories[i].color); read_from_buffer(categories[i].name); } for (int i = 0; i < num_categorized_days; i++) { read_from_buffer(categorized_days[i].year); read_from_buffer(categorized_days[i].month); read_from_buffer(categorized_days[i].day); read_from_buffer(categorized_days[i].category); } #undef read_from_buffer hydro_memzero(plaintext_buffer, sizeof(plaintext_buffer)); char *title = (char *)calloc(1, strlen("Work Calendar - ") + strlen(save_file_path) + 1); memcpy(title, "Work Calendar - ", strlen("Work Calendar - ")); memcpy(title + strlen("Work Calendar - "), save_file_path, strlen(save_file_path)); set_window_title(title); free(title); } void per_frame(){ ImGuiStyle &style = ImGui::GetStyle(); ImVec2 center = ImGui::GetMainViewport()->GetCenter(); year_min_size = ImGui::CalcTextSize("8888").x + 5; ImGuiID main_viewport_dock = ImGui::GetID("main_viewport_dock"); if (!ImGui::DockBuilderGetNode(main_viewport_dock)) { ImGui::DockBuilderAddNode (main_viewport_dock, ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_NoTabBar); ImGui::DockBuilderSetNodePos (main_viewport_dock, ImGui::GetMainViewport()->WorkPos); ImGui::DockBuilderSetNodeSize(main_viewport_dock, ImGui::GetMainViewport()->WorkSize); ImGuiID left_dock = 0; ImGuiID right_dock = ImGui::DockBuilderSplitNode(main_viewport_dock, ImGuiDir_Right, 0.2f, NULL, &left_dock); ImGui::DockBuilderDockWindow("Work Calendar", left_dock); ImGui::DockBuilderDockWindow("Legende", right_dock); ImGui::DockBuilderFinish(main_viewport_dock); } ImGui::DockSpaceOverViewport(main_viewport_dock, ImGui::GetMainViewport(), ImGuiDockNodeFlags_NoTabBar); ImGuiID close_popup = ImGui::GetID("Close"); ImGuiID save_password_popup = ImGui::GetID("Passwort eingeben##Save"); ImGuiID open_password_popup = ImGui::GetID("Passwort eingeben##Open"); if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("Datei")) { if (ImGui::MenuItem("Öffnen", "Ctrl + O")) { char *new_save_file_path = open_file_dialog(); if (new_save_file_path) { if (save_file_path) free(save_file_path); save_file_path = new_save_file_path; ImGui::OpenPopup(open_password_popup); } } if (ImGui::MenuItem("Speichern", "Ctrl + S", false, !!save_file_path)) { save(); } if (ImGui::MenuItem("Speichern unter", "Ctrl + Shift + S")) { char *new_save_file_path = save_file_dialog(); if (new_save_file_path) { if (save_file_path) free(save_file_path); save_file_path = new_save_file_path; ImGui::OpenPopup(save_password_popup); } } //TODO ImGui::Separator(); if (ImGui::MenuItem("Kalender Schließen", "Ctrl + X")) { //TODO if calendar is opened warning if (save_file_path) free(save_file_path); save_file_path = NULL; num_categories = 0; num_categorized_days = 0; hydro_memzero(categories, sizeof(categories)); hydro_memzero(categorized_days, sizeof(categorized_days)); hydro_memzero(derived_key, sizeof(derived_key)); set_window_title("Work Calendar"); } ImGui::Separator(); if (ImGui::MenuItem("Beenden", NULL)) { ImGui::OpenPopup(close_popup); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Ansicht")) { ImGui::MenuItem("Legende", NULL, &legend_visible); ImGui::EndMenu(); } if (ImGui::BeginMenu("Hilfe")) { ImGui::MenuItem("Demo", NULL, &show_demo_window); if (ImGui::MenuItem("Wiki", NULL)) { ImGui::GetPlatformIO().Platform_OpenInShellFn(ImGui::GetCurrentContext(), wiki_url); } ImGui::Separator(); ImGui::MenuItem("Über", NULL); //TODO ImGui::EndMenu(); } ImGui::EndMainMenuBar(); } ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); if (ImGui::BeginPopupModal("Passwort eingeben##Save", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); ImGui::AlignTextToFramePadding(); ImGui::Text("Passwort"); ImGui::SameLine(); ImGui::SetNextItemWidth(-1); ImGui::InputText("##Passwort", password_input_buffer, sizeof(password_input_buffer), ImGuiInputTextFlags_Password); ImGui::AlignTextToFramePadding(); ImGui::Text("Passwort bestätigen"); ImGui::SameLine(); ImGui::SetNextItemWidth(-1); ImGui::InputText("##Passwort bestätigen", password_confirmation_input_buffer, sizeof(password_confirmation_input_buffer), ImGuiInputTextFlags_Password); ImGui::PopStyleVar(); if (ImGui::Button("OK", ImVec2(120, 0))) { if (hydro_compare((uint8_t *)password_input_buffer, (uint8_t *) password_confirmation_input_buffer, sizeof(password_input_buffer)) == 0) { hydro_pwhash_deterministic(derived_key, sizeof derived_key, password_input_buffer, strlen(password_input_buffer), HYDROGEN_CONTEXT, master_key, HYDROGEN_OPSLIMIT, HYDROGEN_MEMLIMIT, HYDROGEN_THREADS); save(); ImGui::CloseCurrentPopup(); hydro_memzero(password_input_buffer, sizeof(password_input_buffer)); hydro_memzero(password_confirmation_input_buffer, sizeof(password_confirmation_input_buffer)); } //TODO color input red + message } ImGui::SameLine(); if (ImGui::Button("Abbrechen", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); hydro_memzero(password_input_buffer, sizeof(password_input_buffer)); hydro_memzero(password_confirmation_input_buffer, sizeof(password_confirmation_input_buffer)); } ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); if (ImGui::BeginPopupModal("Passwort eingeben##Open", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f); ImGui::AlignTextToFramePadding(); ImGui::Text("Passwort"); ImGui::SameLine(); ImGui::SetNextItemWidth(-1); ImGui::InputText("##Passwort", password_input_buffer, sizeof(password_input_buffer), ImGuiInputTextFlags_Password); ImGui::PopStyleVar(); if (ImGui::Button("OK", ImVec2(120, 0))) { hydro_pwhash_deterministic(derived_key, sizeof derived_key, password_input_buffer, strlen(password_input_buffer), HYDROGEN_CONTEXT, master_key, HYDROGEN_OPSLIMIT, HYDROGEN_MEMLIMIT, HYDROGEN_THREADS); hydro_memzero(password_input_buffer, sizeof(password_input_buffer)); load(); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Abbrechen", ImVec2(120, 0))) { if (save_file_path) free(save_file_path); save_file_path = NULL; ImGui::CloseCurrentPopup(); hydro_memzero(password_input_buffer, sizeof(password_input_buffer)); } ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); if (ImGui::BeginPopupModal("Close", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("Möchten Sie wirklich beenden?"); ImGui::SetItemDefaultFocus(); if (ImGui::Button("Ja", ImVec2(120, 0))) { should_exit = true; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button("Nein", ImVec2(120, 0))) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } if (ImGui::Begin("Work Calendar", 0)) { ImGui::PushStyleColor(ImGuiCol_TableHeaderBg, {0,0,0,0}); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, {0,0,0,0}); ImGui::PushStyleColor(ImGuiCol_HeaderActive, {0,0,0,0}); if (ImGui::BeginTable("CalendarHeader", 5)) { ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); // Left side ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); // Prev year ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, year_min_size); // Year ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); // Next year ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch); // Right side ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(1); if (ImGui::ArrowButton("##left", ImGuiDir_Left)) year--; ImGui::TableSetColumnIndex(2); ImGui::TextAligned(0.5, -FLT_MIN, "%d", year); ImGui::TableSetColumnIndex(3); if (ImGui::ArrowButton("##right", ImGuiDir_Right)) year++; ImGui::TableSetColumnIndex(4); ImGui::EndTable(); } for (int month = 1; month <= 12; month++) { ImGui::PushID(month); ImGui::PushStyleColor(ImGuiCol_TableBorderStrong, {0,0,0,0}); ImGui::PushStyleColor(ImGuiCol_TableBorderLight, {0,0,0,0}); if (ImGui::BeginTable("Month", 1, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX)) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::TextAligned(0.5, -FLT_MIN, month_names[month - 1]); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); //calendar week table if (ImGui::BeginTable("CalendarWeek", 1, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_Borders)){ ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::TextAligned(0.5, -FLT_MIN, "KW"); for (int day = 1; day <= days_per_month(year, month); day++) { int weekday = weekday_from_day(year, month, day); if (weekday == 0 || day == 1){ ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); //if first week has 3 or less days, last week from previous year int calender_week = calendar_week_from_day(year, month, day); if (calender_week == 0) { ImGui::TextAligned(0.5, -FLT_MIN, "%d", 53); } else { ImGui::TextAligned(0.5, -FLT_MIN, "%d", calender_week); } } } ImGui::EndTable(); } ImGui::SameLine(0, 0); ImGui::BeginGroup(); ImGui::PushStyleVarY(ImGuiStyleVar_ItemSpacing, 0); if (ImGui::BeginTable("Weekdays", 7, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_Borders)){ ImGui::TableSetupColumn("Mo"); ImGui::TableSetupColumn("Di"); ImGui::TableSetupColumn("Mi"); ImGui::TableSetupColumn("Do"); ImGui::TableSetupColumn("Fr"); ImGui::TableSetupColumn("Sa"); ImGui::TableSetupColumn("So"); ImGui::TableHeadersRow(); ImGui::EndTable(); } ImGui::PopStyleColor(2); ImGui::PushStyleColor(ImGuiCol_TableBorderStrong, {119/255.0f,119/255.0f,119/255.0f,83/255.0f}); ImGui::PushStyleColor(ImGuiCol_TableBorderLight, {119/255.0f,119/255.0f,119/255.0f,83/255.0f}); //days of month table if (ImGui::BeginTable("Weekdays", 7, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_Borders)) { int offset = weekday_from_day(year, month, 1); for (int day = 1; day <= days_per_month(2025, month); day++) { int weekday = weekday_from_day(year, month, day); if (weekday == 0 || day == 1){ ImGui::TableNextRow(); } ImGui::TableSetColumnIndex(weekday); ImU32 cell_bg_color_workweek = ImGui::GetColorU32(table_white_bg); ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, cell_bg_color_workweek); //weekend coloring if (weekday == 5){ ImU32 cell_bg_color_sat = ImGui::GetColorU32(table_saturday_bg); ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, cell_bg_color_sat); } if (weekday == 6){ ImU32 cell_bg_color_sun = ImGui::GetColorU32(table_sunday_bg); ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, cell_bg_color_sun); } //color field with category for (int i = 0; i < num_categorized_days; i++) { if (categorized_days[i].year != year) continue; if (categorized_days[i].month != month) continue; if (categorized_days[i].day != day) continue; ImColor color = categories[categorized_days[i].category].color; color.Value.w = 0.5; //sat + sun: add alpha value if (weekday == 5) color.Value.w = color.Value.w + table_saturday_bg.w; if (weekday == 6) color.Value.w = color.Value.w + table_sunday_bg.w; ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImU32(color)); } if (current_year == year && current_month == month && current_day == day ) { ImGui::PushFont(inter_bold); ImGui::TextAligned(0.5, -FLT_MIN, "%d", day); ImGui::PopFont(); } else { ImGui::TextAligned(0.5, -FLT_MIN, "%d", day); } if (ImGui::IsItemHovered()){ if (selected_category != -1) { ImColor hover_color = categories[selected_category].color; hover_color.Value.w = 0.2; ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGui::GetColorU32(ImU32(hover_color))); } else { ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGui::GetColorU32(table_hover_color)); } } //apply selected color to field if (selected_category != -1 && ImGui::IsItemClicked()) { for (int i = 0; i < num_categorized_days; i++) { if (categorized_days[i].year != year) continue; if (categorized_days[i].month != month) continue; if (categorized_days[i].day != day) continue; if (categorized_days[i].category == selected_category) { categorized_days[i] = categorized_days[num_categorized_days-1]; num_categorized_days--; } else { categorized_days[i] = { .year = year, .month = month, .day = day, .category = selected_category, }; num_categorized_days++; } goto color_changed; } categorized_days[num_categorized_days] = { .year = year, .month = month, .day = day, .category = selected_category, }; num_categorized_days++; } color_changed:; } ImGui::EndTable(); } ImGui::PopStyleColor(2); ImGui::PopStyleVar(); //weekday header table + days of month table ImGui::EndGroup(); //month table ImGui::EndTable(); } if ((month % 4) != 0) ImGui::SameLine(); ImGui::PopID(); } ImGui::PopStyleColor(3); } ImGui::End(); // Legende if (legend_visible && ImGui::Begin("Legende", 0)) { if (ImGui::BeginTable("Legend", 4, ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("Color", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Edit", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Delete", ImGuiTableColumnFlags_WidthFixed ); for (size_t i = 0; i < num_categories; i++) { auto &category = categories[i]; ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); if (ImGui::Selectable("##Category", selected_category == i, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap, { 0, style.FramePadding.y * 2 + ImGui::CalcTextSize("M").y })) { selected_category = selected_category == i ? -1 : i; } ImGui::SameLine(); ImGui::ColorEdit3(category.name, &category.color.Value.x, ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoInputs); ImGui::TableSetColumnIndex(1); if (category.editing) { ImGui::SetNextItemWidth(-1); if (ImGui::InputText("##name", category.name, IM_ARRAYSIZE(category.name), ImGuiInputTextFlags_EnterReturnsTrue)) { category.editing = false; } } else { ImGui::Text("%s", category.name); } ImGui::TableSetColumnIndex(2); if (ImGui::Button("e")) category.editing = !category.editing; ImGui::TableSetColumnIndex(3); if (ImGui::Button("d")) { for (size_t j = 0; j < num_categorized_days; j++) { // Remove categorized days that have the to be deleted category if (categorized_days[j].category == i) { for (size_t k = j; k < num_categorized_days - 1; k++) categorized_days[k] = categorized_days[k + 1]; num_categorized_days--; j--; } // Decrement the category index of all categorized days that have an index higher than the one to be removed if (categorized_days[j].category > i) categorized_days[j].category--; } // Remove the category for (size_t j = i; j < num_categories - 1; j++) categories[j] = categories[j + 1]; num_categories--; } ImGui::PopID(); } ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); if (ImGui::Button("+")) { categories[num_categories] = {}; num_categories++; } ImGui::EndTable(); } } if (legend_visible) ImGui::End(); }