13 Commits

Author SHA1 Message Date
PoliEcho 43d53629ca add selector to timetable. --CONTAINS BUGS--
/ sync-to-origin (push) Has been cancelled
2025-03-09 09:07:54 +01:00
PoliEcho d9fd2d40eb turn HourIdLookupTable into std::vector 2025-03-08 16:06:14 +01:00
PoliEcho 19c45c6969 timetable ui done
/ sync-to-origin (push) Waiting to run
2025-03-08 15:40:44 +01:00
PoliEcho 5c345fb5a1 basic timetable implemented
/ sync-to-origin (push) Waiting to run
2025-03-08 13:16:11 +01:00
PoliEcho 5782e69313 basic timetable ui 2025-03-08 09:13:17 +01:00
PoliEcho ae3485a899 remove junk 2025-03-07 15:29:20 +01:00
PoliEcho 6e9fcd093f add const to print_in_middle functions 2025-03-07 14:00:46 +01:00
PoliEcho d1e34a5c10 add ignoressl toggling
/ sync-to-origin (push) Has been cancelled
2025-03-06 19:45:18 +01:00
PoliEcho 3a78917e10 link ncursesw 2025-03-06 19:42:05 +01:00
PoliEcho a407d8c16a wide string refactor 2025-03-06 19:41:30 +01:00
PoliEcho af5252f732 add basic timetable structure 2025-03-06 18:54:51 +01:00
PoliEcho 87c9b1e125 add timetable menu entery 2025-03-06 18:54:37 +01:00
PoliEcho 41316160ad fix .PHONY 2025-03-06 15:41:13 +01:00
9 changed files with 853 additions and 193 deletions
+3 -3
View File
@@ -1,8 +1,8 @@
# Compiler and flags # Compiler and flags
CPPC = g++ CPPC = g++
CPPC_FLAGS = -std=c++23 -s -O3 -lncurses -lcurl -lmenu -lpanel -Wall -Wextra -Wno-write-strings CPPC_FLAGS = -std=c++23 -s -O3 -lncursesw -lcurl -lmenu -lpanel -Wall -Wextra -Wno-write-strings
# Debug flags: # Debug flags:
# CPPC_FLAGS = -ggdb -std=c++23 -lncurses -lcurl -lmenu -lpanel -Wall -Wextra # CPPC_FLAGS = -ggdb -std=c++23 -lncursesw -lcurl -lmenu -lpanel -Wall -Wextra -Wno-write-strings
SRC_PATH := src SRC_PATH := src
@@ -35,4 +35,4 @@ $(OBJ_PATH)/%.o: $(SRC_PATH)/%.cpp
clean: clean:
rm -fr build rm -fr build
.PHONY: all clean make-build-dir .PHONY: all clean install
+14 -1
View File
@@ -1,6 +1,19 @@
#include <string_view>
#ifndef VERSION #ifndef VERSION
#define VERSION "0.5" #define VERSION "0.6"
#define NAME "bakatui" #define NAME "bakatui"
inline constexpr auto hash_djb2a(const std::string_view sv) {
unsigned long hash{5381};
for (unsigned char c : sv) {
hash = ((hash << 5) + hash) ^ c;
}
return hash;
}
inline constexpr auto operator"" _sh(const char *str, size_t len) {
return hash_djb2a(std::string_view{str, len});
}
#endif #endif
+98 -6
View File
@@ -1,14 +1,18 @@
#include "helper_funcs.h"
#include "color.h"
#include "net.h" #include "net.h"
#include <codecvt>
#include <csignal> #include <csignal>
#include <curses.h> #include <curses.h>
#include <dirent.h> #include <dirent.h>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <locale>
#include <panel.h>
#include <string> #include <string>
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include "color.h"
void safe_exit(int code) { void safe_exit(int code) {
switch (code) { switch (code) {
@@ -53,10 +57,10 @@ std::string SoRAuthFile(bool save, std::string data) {
std::string savedir_path = home; std::string savedir_path = home;
savedir_path.append("/.local/share/bakatui"); savedir_path.append("/.local/share/bakatui");
if (!std::filesystem::exists(savedir_path)) { if (!std::filesystem::exists(savedir_path)) {
if (!std::filesystem::create_directories(savedir_path)) { if (!std::filesystem::create_directories(savedir_path)) {
std::cerr << RED "[ERROR] " RESET << "Failed to create directory: " << savedir_path << "\n"; std::cerr << RED "[ERROR] " RESET
<< "Failed to create directory: " << savedir_path << "\n";
safe_exit(EXIT_FAILURE); safe_exit(EXIT_FAILURE);
} }
} }
@@ -66,7 +70,8 @@ std::string SoRAuthFile(bool save, std::string data) {
if (save) { if (save) {
std::ofstream authfile(authfile_path); std::ofstream authfile(authfile_path);
if (!authfile.is_open()) { if (!authfile.is_open()) {
std::cerr << RED "[ERROR] " RESET << "Failed to open auth file for writing.\n"; std::cerr << RED "[ERROR] " RESET
<< "Failed to open auth file for writing.\n";
safe_exit(EXIT_FAILURE); safe_exit(EXIT_FAILURE);
} }
authfile << data; authfile << data;
@@ -75,7 +80,8 @@ std::string SoRAuthFile(bool save, std::string data) {
} else { } else {
std::ifstream authfile(authfile_path); std::ifstream authfile(authfile_path);
if (!authfile.is_open()) { if (!authfile.is_open()) {
std::cerr << RED "[ERROR] " RESET << "Failed to open auth file for reading.\n"; std::cerr << RED "[ERROR] " RESET
<< "Failed to open auth file for reading.\n";
safe_exit(EXIT_FAILURE); safe_exit(EXIT_FAILURE);
} }
data.assign((std::istreambuf_iterator<char>(authfile)), data.assign((std::istreambuf_iterator<char>(authfile)),
@@ -99,8 +105,9 @@ void get_input_and_login() {
bakaapi::login(username, password); bakaapi::login(username, password);
} }
// Original function
void print_in_middle(WINDOW *win, int starty, int startx, int width, void print_in_middle(WINDOW *win, int starty, int startx, int width,
char *string, chtype color) { const char *string, chtype color) {
int length, x, y; int length, x, y;
float temp; float temp;
@@ -123,8 +130,33 @@ void print_in_middle(WINDOW *win, int starty, int startx, int width,
refresh(); refresh();
} }
// Wide character version
void wprint_in_middle(WINDOW *win, int starty, int startx, int width,
const wchar_t *string, chtype color) {
int length, x, y;
float temp;
if (win == NULL)
win = stdscr;
getyx(win, y, x);
if (startx != 0)
x = startx;
if (starty != 0)
y = starty;
if (width == 0)
width = 80;
length = wcslen(string);
temp = (width - length) / 2;
x = startx + (int)temp;
wattron(win, color);
mvwaddwstr(win, y, x, string);
wattroff(win, color);
refresh();
}
const std::string WHITESPACE = " \n\r\t\f\v"; const std::string WHITESPACE = " \n\r\t\f\v";
const std::wstring WWHITESPACE = L" \n\r\t\f\v";
std::string ltrim(const std::string &s) { std::string ltrim(const std::string &s) {
size_t start = s.find_first_not_of(WHITESPACE); size_t start = s.find_first_not_of(WHITESPACE);
@@ -139,3 +171,63 @@ std::string rtrim(const std::string &s) {
std::string rm_tr_le_whitespace(const std::string &s) { std::string rm_tr_le_whitespace(const std::string &s) {
return rtrim(ltrim(s)); return rtrim(ltrim(s));
} }
// Wide character versions
std::wstring wltrim(const std::wstring &s) {
size_t start = s.find_first_not_of(WWHITESPACE);
return (start == std::wstring::npos) ? L"" : s.substr(start);
}
std::wstring wrtrim(const std::wstring &s) {
size_t end = s.find_last_not_of(WWHITESPACE);
return (end == std::wstring::npos) ? L"" : s.substr(0, end + 1);
}
std::wstring wrm_tr_le_whitespace(const std::wstring &s) {
return wrtrim(wltrim(s));
}
// Conversion utilities
char *wchar_to_char(wchar_t *src) {
if (!src)
return nullptr;
size_t len = wcslen(src) + 1; // +1 for null terminator
char *dest = new char[len * MB_CUR_MAX];
std::wcstombs(dest, src, len * MB_CUR_MAX);
return dest;
}
wchar_t *char_to_wchar(char *src) {
if (!src)
return nullptr;
size_t len = strlen(src) + 1; // +1 for null terminator
wchar_t *dest = new wchar_t[len];
std::mbstowcs(dest, src, len);
return dest;
}
std::wstring string_to_wstring(const std::string &str) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
return converter.from_bytes(str);
}
std::string wstring_to_string(const std::wstring &wstr) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
return converter.to_bytes(wstr);
}
void move_panel_relative(PANEL *panel, int dy, int dx) {
WINDOW *win = panel_window(panel);
int y, x;
getbegyx(win, y, x);
int new_y = y + dy;
int new_x = x + dx;
move_panel(panel, new_y, new_x);
}
+17 -1
View File
@@ -5,12 +5,28 @@
#ifndef _ba_hf_hg_ #ifndef _ba_hf_hg_
#define _ba_hf_hg_ #define _ba_hf_hg_
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
void safe_exit(int code); void safe_exit(int code);
std::string bool_to_string(bool bool_in); std::string bool_to_string(bool bool_in);
std::string SoRAuthFile(bool save, std::string data); std::string SoRAuthFile(bool save, std::string data);
void get_input_and_login(); void get_input_and_login();
// Original functions
void print_in_middle(WINDOW *win, int starty, int startx, int width, void print_in_middle(WINDOW *win, int starty, int startx, int width,
char *string, chtype color); const char *string, chtype color);
std::string rm_tr_le_whitespace(const std::string &s); std::string rm_tr_le_whitespace(const std::string &s);
// Wide character support functions
void wprint_in_middle(WINDOW *win, int starty, int startx, int width,
const wchar_t *string, chtype color);
std::wstring wrm_tr_le_whitespace(const std::wstring &s);
// Conversion utilities
char *wchar_to_char(wchar_t *src);
wchar_t *char_to_wchar(char *src);
std::wstring string_to_wstring(const std::string &str);
std::string wstring_to_string(const std::wstring &wstr);
#endif #endif
+12 -9
View File
@@ -1,23 +1,24 @@
#include "helper_funcs.h" #include "helper_funcs.h"
#include "net.h" #include "net.h"
#include "timetable.h"
#include <cstddef> #include <cstddef>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <curses.h> #include <curses.h>
#include <locale>
#include <menu.h> #include <menu.h>
#include <string>
#include "marks.h" #include "marks.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD 4 #define CTRLD 4
char *choices[] = { wchar_t *choices[] = {
"login", "Marks", "timetable", "Komens", L"login", L"Marks", L"timetable", L"Komens",
"Homework", "Absence", "Exit", (char *)NULL, L"Homework", L"Absence", L"Exit", (wchar_t *)NULL,
};
void (*choicesFuncs[])() = {
nullptr, marks_page, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
}; };
void (*choicesFuncs[])() = {nullptr, marks_page, timetable_page, nullptr,
nullptr, nullptr, nullptr, nullptr};
void main_menu() { void main_menu() {
ITEM **my_items; ITEM **my_items;
@@ -27,6 +28,7 @@ void main_menu() {
int n_choices, i; int n_choices, i;
/* Initialize curses */ /* Initialize curses */
setlocale(LC_ALL, "");
initscr(); initscr();
start_color(); start_color();
cbreak(); cbreak();
@@ -39,7 +41,8 @@ void main_menu() {
n_choices = ARRAY_SIZE(choices); n_choices = ARRAY_SIZE(choices);
my_items = (ITEM **)calloc(n_choices, sizeof(ITEM *)); my_items = (ITEM **)calloc(n_choices, sizeof(ITEM *));
for (i = 0; i < n_choices; ++i) for (i = 0; i < n_choices; ++i)
my_items[i] = new_item(choices[i], choices[i]); my_items[i] =
new_item(wchar_to_char(choices[i]), wchar_to_char(choices[i]));
/* Crate menu */ /* Crate menu */
my_menu = new_menu((ITEM **)my_items); my_menu = new_menu((ITEM **)my_items);
@@ -59,7 +62,7 @@ void main_menu() {
/* Print a border around the main window and print a title */ /* Print a border around the main window and print a title */
box(my_menu_win, 0, 0); box(my_menu_win, 0, 0);
print_in_middle(my_menu_win, 1, 0, 40, "Main Menu", COLOR_PAIR(1)); wprint_in_middle(my_menu_win, 1, 0, 40, L"Main Menu", COLOR_PAIR(1));
mvwaddch(my_menu_win, 2, 0, ACS_LTEE); mvwaddch(my_menu_win, 2, 0, ACS_LTEE);
mvwhline(my_menu_win, 2, 1, ACS_HLINE, 38); mvwhline(my_menu_win, 2, 1, ACS_HLINE, 38);
mvwaddch(my_menu_win, 2, 39, ACS_RTEE); mvwaddch(my_menu_win, 2, 39, ACS_RTEE);
+70 -62
View File
@@ -1,5 +1,7 @@
#include "marks.h" #include "marks.h"
#include "helper_funcs.h" #include "helper_funcs.h"
#include "net.h"
#include <algorithm>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
@@ -7,40 +9,17 @@
#include <curses.h> #include <curses.h>
#include <format> #include <format>
#include <iostream> #include <iostream>
#include <locale>
#include <menu.h> #include <menu.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <panel.h> #include <panel.h>
#include <string> #include <string>
#include <algorithm>
#include "net.h"
using nlohmann::json; using nlohmann::json;
// Thsi code is based on // This code is based on
// https://github.com/tony/NCURSES-Programming-HOWTO-examples/blob/master/16-panels // https://github.com/tony/NCURSES-Programming-HOWTO-examples/blob/master/16-panels
/* // MIT License (see original file)
The MIT License (MIT)
Copyright (c) 2016 Tony Narlock
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#define NLINES 10 #define NLINES 10
#define NCOLS 40 #define NCOLS 40
@@ -51,8 +30,8 @@ SOFTWARE.
#define DEFAULT_PADDING 4 #define DEFAULT_PADDING 4
void init_wins(WINDOW **wins, int n, json marks_json); void init_wins(WINDOW **wins, int n, json marks_json);
void win_show(WINDOW *win, char *label, int label_color, int width, int height, void win_show(WINDOW *win, wchar_t *label, int label_color, int width,
json marks_json, int SubjectIndex); int height, json marks_json, int SubjectIndex);
void marks_page() { void marks_page() {
// DONT FORGET TO UNCOMMENT // DONT FORGET TO UNCOMMENT
@@ -79,8 +58,6 @@ void marks_page() {
noecho(); noecho();
keypad(stdscr, TRUE); keypad(stdscr, TRUE);
std::clog << COLS << " " << LINES << std::endl;
/* Initialize all the colors */ /* Initialize all the colors */
for (uint8_t i = 0; i < 8; i++) { for (uint8_t i = 0; i < 8; i++) {
init_pair(i, i, COLOR_BLACK); init_pair(i, i, COLOR_BLACK);
@@ -98,8 +75,8 @@ void marks_page() {
// Attach panels // Attach panels
for (size_t i = 0; i < size_my_panels; i++) { for (size_t i = 0; i < size_my_panels; i++) {
my_panels[i] = new_panel(my_wins[i]); my_panels[i] = new_panel(my_wins[i]);
set_panel_userptr(my_panels[i], set_panel_userptr(my_panels[i], (i + 1 < size_my_panels) ? my_panels[i + 1]
(i + 1 < size_my_panels) ? my_panels[i+1] : my_panels[0]); : my_panels[0]);
} }
update_panels(); update_panels();
@@ -154,36 +131,48 @@ void marks_page() {
/* Put all the windows */ /* Put all the windows */
void init_wins(WINDOW **wins, int n, json marks_json) { void init_wins(WINDOW **wins, int n, json marks_json) {
int x, y, i; int x, y, i;
char label[1500]; wchar_t label[1500];
y = DEFAULT_Y_OFFSET; y = DEFAULT_Y_OFFSET;
x = DEFAULT_X_OFFSET; x = DEFAULT_X_OFFSET;
uint8_t curent_color = 0; uint8_t curent_color = 0;
int MaxHight = 0; int MaxHight = 0;
// this loop true subjects // this loop through subjects
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
// Calculate label and max_text_length to determine window width // Calculate label and max_text_length to determine window width
std::string sub_name = marks_json["Subjects"][i]["Subject"]["Name"]; std::string sub_name = marks_json["Subjects"][i]["Subject"]["Name"];
// DEBUG
// std::clog << BLUE"[LOG]" << RESET" procesing subject: " << sub_name << "\n";
std::string sub_avg_s = marks_json["Subjects"][i]["AverageText"]; std::string sub_avg_s = marks_json["Subjects"][i]["AverageText"];
sprintf(label, "%s - avg: %s", sub_name.c_str(), sub_avg_s.c_str());
size_t max_text_length = strlen(label); // Convert to wchar_t
std::wstring wsub_name = string_to_wstring(sub_name);
std::wstring wsub_avg_s = string_to_wstring(sub_avg_s);
// Using swprintf for wide character formatting
swprintf(label, sizeof(label) / sizeof(label[0]), L"%ls - avg: %ls",
wsub_name.c_str(), wsub_avg_s.c_str());
size_t max_text_length = wcslen(label);
for (int j = 0; j < marks_json["Subjects"][i]["Marks"].size(); j++) { for (int j = 0; j < marks_json["Subjects"][i]["Marks"].size(); j++) {
std::string caption = rm_tr_le_whitespace(marks_json["Subjects"][i]["Marks"][j]["Caption"]); std::string caption =
std::string theme = rm_tr_le_whitespace(marks_json["Subjects"][i]["Marks"][j]["Theme"]); rm_tr_le_whitespace(marks_json["Subjects"][i]["Marks"][j]["Caption"]);
caption = rm_tr_le_whitespace(caption); std::string theme =
theme = rm_tr_le_whitespace(theme); rm_tr_le_whitespace(marks_json["Subjects"][i]["Marks"][j]["Theme"]);
std::wstring wcaption = string_to_wstring(caption);
std::wstring wtheme = string_to_wstring(theme);
// Some code that does something and fixes some edge cases
std::string testCaption = caption + std::format(" {{{}}} [{}]", "X", 0);
std::wstring wTestCaption = string_to_wstring(testCaption);
max_text_length = max_text_length =
std::max({max_text_length, caption.length(), theme.length()}); std::max({max_text_length, wTestCaption.length(), wtheme.length()});
} }
int width = max_text_length + DEFAULT_PADDING; int width = max_text_length + DEFAULT_PADDING;
// hanndle windows overflowing off screen // handle windows overflowing off screen
if (x + width > COLS) { if (x + width > COLS) {
x = DEFAULT_X_OFFSET; x = DEFAULT_X_OFFSET;
y += MaxHight + 2; y += MaxHight + 2;
@@ -207,8 +196,8 @@ void init_wins(WINDOW **wins, int n, json marks_json) {
} }
/* Show the window with a border and a label */ /* Show the window with a border and a label */
void win_show(WINDOW *win, char *label, int label_color, int width, int height, void win_show(WINDOW *win, wchar_t *label, int label_color, int width,
json marks_json, int SubjectIndex) { int height, json marks_json, int SubjectIndex) {
int startx, starty; int startx, starty;
wresize(win, height, width); wresize(win, height, width);
@@ -221,29 +210,48 @@ void win_show(WINDOW *win, char *label, int label_color, int width, int height,
mvwhline(win, 2, 1, ACS_HLINE, width - 2); mvwhline(win, 2, 1, ACS_HLINE, width - 2);
mvwaddch(win, 2, width - 1, ACS_RTEE); mvwaddch(win, 2, width - 1, ACS_RTEE);
print_in_middle(win, 1, 0, width, label, COLOR_PAIR(label_color)); wprint_in_middle(win, 1, 0, width, label, COLOR_PAIR(label_color));
char CaptionBuf[1500]; wchar_t CaptionBuf[1500];
char ThemeBuf[1500]; wchar_t ThemeBuf[1500];
std::string Caption; std::wstring wCaption;
int AdditionalOffset = 0; int AdditionalOffset = 0;
for (size_t i = 0; i < marks_json["Subjects"][SubjectIndex]["Marks"].size(); for (size_t i = 0; i < marks_json["Subjects"][SubjectIndex]["Marks"].size();
i++) { i++) {
Caption = marks_json["Subjects"][SubjectIndex]["Marks"][i]["Caption"]; std::string Caption =
marks_json["Subjects"][SubjectIndex]["Marks"][i]["Caption"];
Caption = rm_tr_le_whitespace(Caption); Caption = rm_tr_le_whitespace(Caption);
Caption.append(std::format(" - {{{}}} [{}]",
marks_json["Subjects"][SubjectIndex]["Marks"][i]["MarkText"]
.get<std::string>(),marks_json["Subjects"][SubjectIndex]["Marks"][i]["Weight"].get<int>()));
strncpy(CaptionBuf, Caption.c_str(), sizeof(CaptionBuf)-1); std::string MarkText =
print_in_middle(win, 3 + i + AdditionalOffset, 0, width, CaptionBuf,COLOR_PAIR(label_color)); marks_json["Subjects"][SubjectIndex]["Marks"][i]["MarkText"];
int Weight = marks_json["Subjects"][SubjectIndex]["Marks"][i]["Weight"];
strncpy(ThemeBuf, // Create formatted string with mark and weight
rm_tr_le_whitespace(marks_json["Subjects"][SubjectIndex]["Marks"][i]["Theme"] std::string formattedCaption =
.get<std::string>()) Caption + std::format(" - {{{}}} [{}]", MarkText, Weight);
.c_str(),
sizeof(ThemeBuf)-1); // Convert to wide string
print_in_middle(win, 3 + i + 1 + AdditionalOffset, 0, width, ThemeBuf, wCaption = string_to_wstring(formattedCaption);
wcsncpy(CaptionBuf, wCaption.c_str(),
sizeof(CaptionBuf) / sizeof(CaptionBuf[0]) - 1);
CaptionBuf[sizeof(CaptionBuf) / sizeof(CaptionBuf[0]) - 1] =
L'\0'; // Ensure null termination
wprint_in_middle(win, 3 + i + AdditionalOffset, 0, width, CaptionBuf,
COLOR_PAIR(label_color));
std::string Theme =
marks_json["Subjects"][SubjectIndex]["Marks"][i]["Theme"];
std::wstring wTheme = string_to_wstring(rm_tr_le_whitespace(Theme));
wcsncpy(ThemeBuf, wTheme.c_str(),
sizeof(ThemeBuf) / sizeof(ThemeBuf[0]) - 1);
ThemeBuf[sizeof(ThemeBuf) / sizeof(ThemeBuf[0]) - 1] =
L'\0'; // Ensure null termination
wprint_in_middle(win, 3 + i + 1 + AdditionalOffset, 0, width, ThemeBuf,
COLOR_PAIR(label_color)); COLOR_PAIR(label_color));
AdditionalOffset++; AdditionalOffset++;
} }
+18 -15
View File
@@ -1,5 +1,6 @@
#include "net.h" #include "net.h"
#include "color.h" #include "color.h"
#include "const.h"
#include "helper_funcs.h" #include "helper_funcs.h"
#include "main.h" #include "main.h"
#include <cerrno> #include <cerrno>
@@ -16,7 +17,6 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json_fwd.hpp>
#include <string> #include <string>
#include "const.h"
using nlohmann::json; using nlohmann::json;
@@ -38,9 +38,8 @@ size_t WriteCallback(void *contents, size_t size, size_t nmemb,
return totalSize; return totalSize;
} }
std::tuple<std::string, int> send_curl_request(std::string endpoint, std::tuple<std::string, int>
uint8_t type, send_curl_request(std::string endpoint, uint8_t type, std::string req_data) {
std::string req_data) {
std::string response; std::string response;
std::string url = baka_api_url + endpoint; std::string url = baka_api_url + endpoint;
if (type == GET) { if (type == GET) {
@@ -54,14 +53,18 @@ std::tuple<std::string, int> send_curl_request(std::string endpoint,
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
if (config.ignoressl) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
struct curl_slist *headers = NULL; struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded"); headers = curl_slist_append(
headers = curl_slist_append(headers, std::format("User-Agent: bakatui/{}", VERSION).c_str()); headers, "Content-Type: application/x-www-form-urlencoded");
headers = curl_slist_append(
headers, std::format("User-Agent: bakatui/{}", VERSION).c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
@@ -87,8 +90,6 @@ std::tuple<std::string, int> send_curl_request(std::string endpoint,
int http_code = 0; int http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
return {response, http_code}; return {response, http_code};
} }
namespace bakaapi { namespace bakaapi {
@@ -141,7 +142,8 @@ void refresh_access_token() {
refresh_token); refresh_token);
// DEBUG // DEBUG
std::clog << "calling send_curl_request() with folowing req_data\n" << req_data << std::endl; std::clog << "calling send_curl_request() with folowing req_data\n"
<< req_data << std::endl;
auto [response, http_code] = send_curl_request("api/login", POST, req_data); auto [response, http_code] = send_curl_request("api/login", POST, req_data);
if (http_code != 200) { if (http_code != 200) {
std::cerr << RED "[ERROR] " << RESET << http_code std::cerr << RED "[ERROR] " << RESET << http_code
@@ -156,7 +158,7 @@ void refresh_access_token() {
access_token = resp_parsed["access_token"]; access_token = resp_parsed["access_token"];
} }
void is_access_token_empty() { void is_access_token_empty() {
if(access_token.empty()) { if (access_token.empty()) {
json authfile_parsed = json::parse(SoRAuthFile(false, "")); json authfile_parsed = json::parse(SoRAuthFile(false, ""));
access_token = authfile_parsed["access_token"]; access_token = authfile_parsed["access_token"];
} }
@@ -166,14 +168,15 @@ void is_access_token_empty() {
json get_data_from_endpoint(std::string endpoint) { json get_data_from_endpoint(std::string endpoint) {
is_access_token_empty(); is_access_token_empty();
std::string req_data = std::string req_data =
std::format("Authorization=Bearer&access_token={}", std::format("Authorization=Bearer&access_token={}", access_token);
access_token);
auto [response, http_code] = send_curl_request(endpoint, GET, req_data); auto [response, http_code] = send_curl_request(endpoint, GET, req_data);
if(http_code != 200) { if (http_code != 200) {
// DEBUG // DEBUG
std::clog << "Failed geting data from endpoint: " << endpoint << " code: " << http_code << "\nrequest: " << req_data <<"\nresponse: " << response << std::endl; std::clog << "Failed geting data from endpoint: " << endpoint
<< " code: " << http_code << "\nrequest: " << req_data
<< "\nresponse: " << response << std::endl;
refresh_access_token(); refresh_access_token();
} }
+399 -12
View File
@@ -1,32 +1,419 @@
#include "timetable.h" #include "timetable.h"
#include <cstdint> #include "color.h"
#include <nlohmann/json.hpp> #include "const.h"
#include "helper_funcs.h"
#include "net.h" #include "net.h"
#include "types.h"
#include <cstdint>
#include <curses.h>
#include <cwchar>
#include <fstream> #include <fstream>
#include <iostream>
#include <ncurses.h>
#include <nlohmann/json.hpp>
#include <panel.h>
#include <string>
#include <vector>
using nlohmann::json; using nlohmann::json;
#define BOTTOM_PADDING 3
#define NLINES 10 #define DEFAULT_OFFSET 3
#define NCOLS 40
#define DEFAULT_X_OFFSET 10 const wchar_t *day_abriviations[] = {nullptr, L"Mo", L"Tu", L"We",
#define DEFAULT_Y_OFFSET 2 L"Th", L"Fr", L"Sa", L"Su"};
#define DEFAULT_PADDING 4 void draw_grid(const uint8_t num_of_columns, const uint8_t num_of_rows,
const uint16_t cell_width, const uint16_t cell_height);
uint8_t hour_id_to_index(const std::vector<uint8_t> &HourIdLookupTable,
uint8_t id) {
for (uint8_t i = 0; i < HourIdLookupTable.size(); i++) {
if (HourIdLookupTable[i] == id) {
return i;
}
}
return 0;
}
json *partial_json_by_id(json &resp_from_api, const std::string &what,
const std::string &id) {
for (uint8_t i = 0; i < resp_from_api[what].size(); i++) {
if (resp_from_api[what][i]["Id"].get<std::string>() == id) {
return &resp_from_api[what][i];
}
}
return nullptr;
}
std::wstring get_data_for_atom(json &resp_from_api, json *atom,
const std::string &from_where,
const std::string &id_key,
const std::string &what) {
return string_to_wstring(
partial_json_by_id(resp_from_api, from_where,
atom->at(id_key).get<std::string>())
->at(what)
.get<std::string>());
}
void timetable_page() { void timetable_page() {
// DONT FORGET TO UNCOMMENT // DONT FORGET TO UNCOMMENT
// json resp_from_api = bakaapi::get_data_from_endpoint("api/3/timetable/actual"); // json resp_from_api =
// bakaapi::get_data_from_endpoint("api/3/timetable/actual");
std::ifstream f("test-data/timetable.json"); std::ifstream f("test-data/timetable.json");
json resp_from_api = json::parse(f); json resp_from_api = json::parse(f);
// calculate table size // this may be unnecessary but i dont have enaugh data to test it
// it sorts the hours by start time
std::vector<uint8_t> HourIdLookupTable(resp_from_api["Hours"].size());
{
using Id_and_Start_time = std::tuple<uint8_t, std::string>;
// ID, start_time
uint8_t column_number = 0; Id_and_Start_time *temp_hour_sorting_array =
for(uint8_t i = 0; i < resp_from_api["Days"].size(); i++) { new Id_and_Start_time[resp_from_api["Hours"].size()];
(resp_from_api["Days"][i]["Atoms"].size() > column_number) ? (column_number = resp_from_api["Days"][i]["Atoms"].size());
for (uint8_t i = 0; i < resp_from_api["Hours"].size(); i++) {
temp_hour_sorting_array[i] = std::make_tuple(
resp_from_api["Hours"][i]["Id"].get<uint8_t>(),
resp_from_api["Hours"][i]["BeginTime"].get<std::string>());
};
std::sort(temp_hour_sorting_array,
temp_hour_sorting_array + resp_from_api["Hours"].size(),
[](const Id_and_Start_time &a, const Id_and_Start_time &b) {
const std::string &str_a = std::get<1>(a);
const std::string &str_b = std::get<1>(b);
const size_t colon_pos_a = str_a.find(':');
const size_t colon_pos_b = str_b.find(':');
if (colon_pos_a == std::string::npos ||
colon_pos_b == std::string::npos) {
std::cerr << RED "[ERROR]" << RESET
<< " Colon not found in time string\n";
safe_exit(EXIT_FAILURE);
} }
const std::string hour_a_S = str_a.substr(0, colon_pos_a);
const std::string hour_b_S = str_b.substr(0, colon_pos_b);
const std::string minute_a_S = str_a.substr(colon_pos_a + 1);
const std::string minute_b_S = str_b.substr(colon_pos_b + 1);
const uint8_t hour_a = std::stoi(hour_a_S);
const uint8_t hour_b = std::stoi(hour_b_S);
const uint8_t minute_a = std::stoi(minute_a_S);
const uint8_t minute_b = std::stoi(minute_b_S);
return (hour_a < hour_b) ||
((hour_a == hour_b) && (minute_a < minute_b));
});
for (uint8_t i = 0; i < resp_from_api["Hours"].size(); i++) {
HourIdLookupTable[i] = std::get<0>(temp_hour_sorting_array[i]);
}
delete[] temp_hour_sorting_array;
}
// some lambda dark magic
const uint8_t num_of_columns = [&]() -> uint8_t {
uint8_t result = 0;
for (uint8_t i = 0; i < resp_from_api["Days"].size(); i++) {
for (uint8_t j = 0; j < resp_from_api["Days"][i]["Atoms"].size(); j++) {
if (hour_id_to_index(
HourIdLookupTable,
resp_from_api["Days"][i]["Atoms"][j]["HourId"].get<uint8_t>()) >
result) {
result = hour_id_to_index(
HourIdLookupTable,
resp_from_api["Days"][i]["Atoms"][j]["HourId"].get<uint8_t>());
}
}
}
return result + 1;
}();
const uint8_t num_of_days = resp_from_api["Days"].size();
setlocale(LC_ALL, "");
/* Initialize curses */
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
curs_set(0);
/* Initialize all the colors */
for (uint8_t i = 0; i < 8; i++) {
init_pair(i, i, COLOR_BLACK);
}
const uint16_t cell_width = (COLS - BOTTOM_PADDING) / num_of_columns;
const uint16_t cell_height = (LINES - BOTTOM_PADDING) / num_of_days;
WINDOW **day_windows = new WINDOW *[num_of_days];
WINDOW **lesson_windows = new WINDOW *[num_of_columns];
std::vector<std::vector<WINDOW *>> cells(
num_of_days, std::vector<WINDOW *>(num_of_columns));
for (uint8_t i = 0; i < num_of_days; i++) {
day_windows[i] = newwin(cell_height, DEFAULT_OFFSET,
i * cell_height + DEFAULT_OFFSET, 0);
// this wont draw left boarder window making it so it looks partially
// offscreen
wborder(day_windows[i], ' ', 0, 0, 0, ACS_HLINE, 0, ACS_HLINE, 0);
const wchar_t *day_abriv =
day_abriviations[resp_from_api["Days"][i]["DayOfWeek"].get<uint8_t>()];
wprint_in_middle(day_windows[i], cell_height / 2, 0, wcslen(day_abriv),
day_abriv, COLOR_PAIR(0));
wrefresh(day_windows[i]);
}
for (uint8_t i = 0; i < num_of_columns; i++) {
lesson_windows[i] =
newwin(DEFAULT_OFFSET, cell_width, 0, i * cell_width + DEFAULT_OFFSET);
wborder(lesson_windows[i], 0, 0, ' ', 0, ACS_VLINE, ACS_VLINE, 0, 0);
std::wstring caption;
std::wstring start_time;
std::wstring end_time;
for (uint8_t j = 0; j < resp_from_api["Hours"].size(); j++) {
if (resp_from_api["Hours"][j]["Id"].get<uint8_t>() ==
HourIdLookupTable[i]) {
// DEBUG
// std::clog <<
// resp_from_api["Hours"][j]["Caption"].get<std::string>();
std::string caption_ascii =
resp_from_api["Hours"][j]["Caption"].get<std::string>();
std::string start_time_ascii =
resp_from_api["Hours"][j]["BeginTime"].get<std::string>();
std::string end_time_ascii =
resp_from_api["Hours"][j]["EndTime"].get<std::string>();
caption = string_to_wstring(caption_ascii);
start_time = string_to_wstring(start_time_ascii);
end_time = string_to_wstring(end_time_ascii);
goto hour_id_found;
}
}
std::cerr << RED "[ERROR]" << RESET " Hour with id " << HourIdLookupTable[i]
<< " not found\n";
safe_exit(128);
hour_id_found:
wprint_in_middle(lesson_windows[i], 0, cell_width / 2, caption.length(),
caption.c_str(), COLOR_PAIR(0));
wprint_in_middle(lesson_windows[i], 1, 1, start_time.length(),
start_time.c_str(), COLOR_PAIR(0));
print_in_middle(lesson_windows[i], 1, cell_width / 2, 1, "-",
COLOR_PAIR(0));
wprint_in_middle(lesson_windows[i], 1, cell_width - end_time.length() - 1,
end_time.length(), end_time.c_str(), COLOR_PAIR(0));
wrefresh(lesson_windows[i]);
}
for (uint8_t i = 0; i < num_of_days; i++) {
for (uint8_t j = 0; j < num_of_columns; j++) {
cells[i][j] =
newwin(cell_height, cell_width, i * cell_height + DEFAULT_OFFSET,
j * cell_width + DEFAULT_OFFSET);
json *atom;
for (uint8_t k = 0; k < resp_from_api["Days"][i]["Atoms"].size(); k++) {
if (resp_from_api["Days"][i]["Atoms"][k]["HourId"].get<uint8_t>() ==
HourIdLookupTable[j]) {
atom = &resp_from_api["Days"][i]["Atoms"][k];
goto correct_atom_found;
}
}
continue;
correct_atom_found:
std::wstring Subject_Abbrev;
std::wstring Room_Abbrev;
std::wstring Teacher_Abbrev;
try {
if (atom->contains("Change") && !atom->at("Change").is_null()) {
switch (
hash_djb2a(atom->at("Change")["ChangeType"].get<std::string>())) {
case "Canceled"_sh:
case "Removed"_sh:
wattron(cells[i][j], COLOR_PAIR(COLOR_GREEN));
box(cells[i][j], 0, 0);
wattroff(cells[i][j], COLOR_PAIR(COLOR_GREEN));
break;
case "RoomChanged"_sh:
case "Substitution"_sh:
wattron(cells[i][j], COLOR_PAIR(COLOR_YELLOW));
box(cells[i][j], 0, 0);
wattroff(cells[i][j], COLOR_PAIR(COLOR_YELLOW));
break;
case "Added"_sh:
wattron(cells[i][j], COLOR_PAIR(COLOR_BLUE));
box(cells[i][j], 0, 0);
wattroff(cells[i][j], COLOR_PAIR(COLOR_BLUE));
break;
default:
// TODO add error handling
__asm__("nop");
}
if (!atom->at("Change")["TypeAbbrev"].is_null()) {
Subject_Abbrev = string_to_wstring(
atom->at("Change")["TypeAbbrev"].get<std::string>());
}
} else {
box(cells[i][j], 0, 0);
}
if (Subject_Abbrev.empty()) {
try {
Subject_Abbrev = get_data_for_atom(resp_from_api, atom, "Subjects",
"SubjectId", "Abbrev");
} catch (...) {
__asm__("nop");
}
}
try {
Room_Abbrev = get_data_for_atom(resp_from_api, atom, "Rooms",
"RoomId", "Abbrev");
} catch (...) {
__asm__("nop");
}
try {
Teacher_Abbrev = get_data_for_atom(resp_from_api, atom, "Teachers",
"TeacherId", "Abbrev");
} catch (...) {
__asm__("nop");
}
wprint_in_middle(cells[i][j], cell_height / 2,
cell_width / 2 - wcslen(Subject_Abbrev.c_str()) / 2,
wcslen(Subject_Abbrev.c_str()), Subject_Abbrev.c_str(),
COLOR_PAIR(0));
wprint_in_middle(cells[i][j], cell_height - 2,
cell_width - wcslen(Room_Abbrev.c_str()) - 1,
wcslen(Room_Abbrev.c_str()), Room_Abbrev.c_str(),
COLOR_PAIR(0));
wprint_in_middle(cells[i][j], cell_height - 2, 1,
wcslen(Teacher_Abbrev.c_str()), Teacher_Abbrev.c_str(),
COLOR_PAIR(0));
wrefresh(cells[i][j]);
} catch (const std::exception &e) {
std::cerr << RED "[ERROR]" << RESET " " << e.what() << "\n";
// world's best error handling
__asm__("nop");
}
}
}
refresh();
SelectorType selected_cell(0, 0, 0, num_of_columns - 1, 0, num_of_days - 1);
std::array<WINDOW *, 4> selector_windows;
std::array<PANEL *, 4> selector_panels;
{
const chtype corners[] = {
ACS_ULCORNER, /* Upper left corner */
ACS_URCORNER, /* Upper right corner */
ACS_LLCORNER, /* Lower left corner */
ACS_LRCORNER /* Lower right corner */
};
unsigned short x_offset, y_offset;
for (uint8_t i = 0; i < selector_windows.size(); i++) {
if (!(i % 2 == 0)) {
x_offset = cell_width - 1;
} else {
x_offset = 0;
}
if (!(i < 2)) {
y_offset = cell_height - 1;
} else {
y_offset = 0;
}
selector_windows[i] =
newwin(1, 1, DEFAULT_OFFSET + y_offset, DEFAULT_OFFSET + x_offset);
selector_panels[i] = new_panel(selector_windows[i]);
wattron(selector_windows[i], COLOR_PAIR(COLOR_RED));
mvwaddch(selector_windows[i], 0, 0, corners[i]);
wattroff(selector_windows[i], COLOR_PAIR(COLOR_RED));
}
}
update_panels();
doupdate();
int ch;
while ((ch = getch()) != KEY_F(1)) {
run_loop_again:
switch (ch) {
case KEY_UP:
case 'k':
selected_cell.y--;
break;
case KEY_DOWN:
case 'j':
selected_cell.y++;
break;
case KEY_LEFT:
case 'h':
selected_cell.x--;
break;
case KEY_RIGHT:
case 'l':
selected_cell.x++;
break;
}
{ // print selected indicator
chtype top_left_corner =
mvwinch(cells[selected_cell.y][selected_cell.x], 0, 0);
if (!((top_left_corner & A_CHARTEXT) == 32)) {
for (uint8_t i = 0; i < selector_panels.size(); i++) {
unsigned short x_offset, y_offset;
if (!(i % 2 == 0)) {
x_offset = cell_width - 1;
} else {
x_offset = 0;
}
if (!(i < 2)) {
y_offset = cell_height - 1;
} else {
y_offset = 0;
}
move_panel(selector_panels[i],
DEFAULT_OFFSET + y_offset + selected_cell.y * cell_height,
DEFAULT_OFFSET + x_offset + selected_cell.x * cell_width);
}
for (uint8_t i = 0; i < num_of_days; i++) {
for (uint8_t j = 0; j < num_of_columns; j++) {
wrefresh(cells[i][j]);
}
}
update_panels();
doupdate();
} else {
// skip if the cell is empty
goto run_loop_again;
}
}
}
delete[] day_windows;
delete[] lesson_windows;
endwin();
} }
+138
View File
@@ -2,8 +2,146 @@
#ifndef _ba_ty_hg_ #ifndef _ba_ty_hg_
#define _ba_ty_hg_ #define _ba_ty_hg_
#include <istream>
struct Config { struct Config {
bool verbose = false; bool verbose = false;
bool ignoressl = false; bool ignoressl = false;
}; };
class LimitedInt {
private:
int value;
const int min_value;
const int max_value;
void setValue(int new_value) {
int range = max_value - min_value + 1;
if (new_value < min_value || new_value > max_value) {
new_value = ((new_value - min_value) % range + range) % range + min_value;
}
value = new_value;
}
public:
// Constructors
LimitedInt(int initial_value = 0, int min = std::numeric_limits<int>::min(),
int max = std::numeric_limits<int>::max())
: min_value(min), max_value(max) {
if (min >= max)
throw std::invalid_argument("Min must be less than max");
setValue(initial_value);
}
// Conversion operators
operator int() const { return value; } // Implicit conversion to int
// Assignment operators
LimitedInt &operator=(int rhs) {
setValue(rhs);
return *this;
}
// Compound assignment with BoundedInt
LimitedInt &operator+=(const LimitedInt &rhs) {
setValue(value + rhs.value);
return *this;
}
// Compound assignment with int
LimitedInt &operator+=(int rhs) {
setValue(value + rhs);
return *this;
}
// Similarly define -=, *=, /=, %=, &=, |=, ^=, <<=, >>= for both types
// Increment/decrement
LimitedInt &operator++() { // Prefix ++
setValue(value + 1);
return *this;
}
LimitedInt operator++(int) { // Postfix ++
LimitedInt temp = *this;
setValue(value + 1);
return temp;
}
LimitedInt &operator--() { // Prefix --
setValue(value - 1);
return *this;
}
LimitedInt operator--(int) { // Postfix --
LimitedInt temp = *this;
setValue(value - 1);
return temp;
}
// Binary arithmetic operators
friend LimitedInt operator+(const LimitedInt &lhs, const LimitedInt &rhs) {
LimitedInt result = lhs;
result += rhs;
return result;
}
friend LimitedInt operator+(const LimitedInt &lhs, int rhs) {
LimitedInt result = lhs;
result += rhs;
return result;
}
friend LimitedInt operator+(int lhs, const LimitedInt &rhs) {
LimitedInt result(lhs, rhs.min_value, rhs.max_value);
result += rhs;
return result;
}
// Similarly define -, *, /, %, &, |, ^, <<, >> for all combinations
// Unary operators
LimitedInt operator-() const {
return LimitedInt(-value, min_value, max_value);
}
LimitedInt operator+() const { return *this; }
// Comparison operators
friend bool operator==(const LimitedInt &lhs, const LimitedInt &rhs) {
return lhs.value == rhs.value;
}
friend bool operator!=(const LimitedInt &lhs, const LimitedInt &rhs) {
return lhs.value != rhs.value;
}
friend bool operator<(const LimitedInt &lhs, const LimitedInt &rhs) {
return lhs.value < rhs.value;
}
// Similarly define >, <=, >= for both BoundedInt and int comparisons
// Stream operators
friend std::ostream &operator<<(std::ostream &os, const LimitedInt &bi) {
os << bi.value;
return os;
}
friend std::istream &operator>>(std::istream &is, LimitedInt &bi) {
int temp;
is >> temp;
bi.setValue(temp);
return is;
}
};
struct SelectorType {
LimitedInt x;
LimitedInt y;
SelectorType(int xArg, int yArg, int min_limit_x, int max_limit_x,
int min_limit_y, int max_limit_y)
: x(xArg, min_limit_x, max_limit_x), y(yArg, min_limit_y, max_limit_y) {}
};
#endif #endif