48 Commits

Author SHA1 Message Date
PoliEcho 52bea2e3c5 fix SEGFAULT when nullptr
/ sync-to-origin (push) Has been cancelled
2025-04-01 09:32:38 +02:00
PoliEcho 22333b6593 version 0.7.1
/ sync-to-origin (push) Has been cancelled
2025-04-01 09:05:28 +02:00
PoliEcho 096b1d1eb6 add memory safety
/ sync-to-origin (push) Waiting to run
2025-03-31 20:26:56 +02:00
PoliEcho dfa0774242 fix type 2025-03-31 19:33:24 +02:00
PoliEcho 24aa979a69 add week selection
/ sync-to-origin (push) Waiting to run
2025-03-31 18:20:58 +02:00
PoliEcho 64b4797908 make timetable request specific date 2025-03-31 11:27:48 +02:00
PoliEcho 522b6fa517 remove useless print statement
/ sync-to-origin (push) Has been cancelled
2025-03-13 11:25:24 +01:00
PoliEcho bee49ee7dc add help text
/ sync-to-origin (push) Waiting to run
2025-03-12 17:26:52 +01:00
PoliEcho accc2a79bc add install rule to Makefile 2025-03-12 17:07:50 +01:00
PoliEcho 6e573ab992 version 0.7
/ sync-to-origin (push) Has been cancelled
2025-03-12 16:58:11 +01:00
PoliEcho 941bbcdb5b make better debug build rule 2025-03-12 16:57:35 +01:00
PoliEcho 925b42ceb7 fix days drawing bug 2025-03-12 16:56:32 +01:00
PoliEcho 4c0abe5464 Update README.md
/ sync-to-origin (push) Waiting to run
2025-03-12 14:07:35 +00:00
PoliEcho e4670f1b26 remove unnessery comments
/ sync-to-origin (push) Waiting to run
2025-03-12 15:04:11 +01:00
PoliEcho 58e9428299 fix improper function usage 2025-03-12 14:19:45 +01:00
PoliEcho 5fc24393c5 fix posible division by zero 2025-03-12 10:33:50 +01:00
PoliEcho 04f0ef76ee fix all real warnings 2025-03-11 11:36:23 +01:00
PoliEcho 17e850e6d7 add infobox but it contains a BUG
/ sync-to-origin (push) Has been cancelled
2025-03-09 16:33:19 +01:00
PoliEcho 01cf082351 Merge branch 'master' of https://git.pupes.org/PoliEcho/bakatui
/ sync-to-origin (push) Waiting to run
2025-03-09 09:48:34 +01:00
PoliEcho 5088b273b6 fixed timetable selector rendering bug 2025-03-09 09:46:45 +01:00
PoliEcho 17f4316947 Update README.md
/ sync-to-origin (push) Waiting to run
2025-03-09 08:23:12 +00:00
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
PoliEcho 2f639b924a update Makefile
/ sync-to-origin (push) Waiting to run
2025-03-06 15:35:59 +01:00
PoliEcho a6ff11ff71 add basic timetable
/ sync-to-origin (push) Waiting to run
2025-03-06 15:10:31 +01:00
PoliEcho 87c2e2e01c remove useless includes 2025-03-06 15:09:53 +01:00
PoliEcho 98b6b8df7e add flag handling and header guards 2025-03-06 14:12:10 +01:00
PoliEcho 0d11dbec32 tiny refactor 2025-03-06 12:00:26 +01:00
PoliEcho 4f852161aa add more explenation text to marks 2025-03-04 12:33:16 +01:00
PoliEcho 50447ac8cd add warning to README
/ sync-to-origin (push) Has been cancelled
2025-03-04 10:32:29 +01:00
PoliEcho d81876f527 remove DEBUG
/ sync-to-origin (push) Waiting to run
2025-03-04 10:26:46 +01:00
PoliEcho 05d3819d56 fix marks page
/ sync-to-origin (push) Waiting to run
2025-03-04 10:24:44 +01:00
PoliEcho 6985b21d37 fixed 1 segfault
/ sync-to-origin (push) Has been cancelled
2025-01-29 14:55:22 +01:00
PoliEcho 4ba8710a71 start of network implementation
/ sync-to-origin (push) Has been cancelled
2025-01-27 16:36:12 +01:00
PoliEcho bd01b506f0 add basic scroling 2025-01-27 10:27:16 +01:00
PoliEcho 4c383fce4c add mark Themes and variable window hight
/ sync-to-origin (push) Waiting to run
2025-01-26 18:07:50 +01:00
PoliEcho 86b9ea812d remove unnessesery line of code 2025-01-26 14:53:21 +01:00
22 changed files with 1440 additions and 162 deletions
+1
View File
@@ -4,3 +4,4 @@ compile_commands.json
core* core*
build build
test-data test-data
log
+10 -6
View File
@@ -1,8 +1,7 @@
# Compiler and flags # Compiler and flags
CPPC = g++ CPPC = g++
CPPC_FLAGS = -std=c++23 -s -O3 -lncurses -lcurl -lmenu -lpanel -Wall -Wextra CPPC_FLAGS = -std=c++23 -s -O3 -lncursesw -lcurl -lmenu -lpanel -Wall -Wextra -Wno-write-strings
# Debug flags: DEBUG_FLAGS = -ggdb -std=c++23 -lncursesw -lcurl -lmenu -lpanel -Wall -Wextra -Wno-write-strings
# CPPC_FLAGS = -ggdb -std=c++23 -lncurses -lcurl -lmenu -lpanel -Wall -Wextra
SRC_PATH := src SRC_PATH := src
@@ -11,16 +10,18 @@ BIN_PATH := build/bin
SRC_FILES := $(shell find $(SRC_PATH) -name '*.cpp') SRC_FILES := $(shell find $(SRC_PATH) -name '*.cpp')
# Generate corresponding object file paths by replacing src/ with build/obj/
OBJ_FILES := $(patsubst $(SRC_PATH)/%.cpp,$(OBJ_PATH)/%.o,$(SRC_FILES)) OBJ_FILES := $(patsubst $(SRC_PATH)/%.cpp,$(OBJ_PATH)/%.o,$(SRC_FILES))
all: make-build-dir $(BIN_PATH)/bakatui all: make-build-dir $(BIN_PATH)/bakatui
debug: CPPC_FLAGS = $(DEBUG_FLAGS)
debug: make-build-dir $(BIN_PATH)/bakatui
make-build-dir: make-build-dir:
mkdir -p $(OBJ_PATH) mkdir -p $(OBJ_PATH)
mkdir -p $(OBJ_PATH)/marks
mkdir -p $(BIN_PATH) mkdir -p $(BIN_PATH)
@@ -32,7 +33,10 @@ $(OBJ_PATH)/%.o: $(SRC_PATH)/%.cpp
$(CPPC) $(CPPC_FLAGS) -c $< -o $@ $(CPPC) $(CPPC_FLAGS) -c $< -o $@
install:
@install -vpm 755 -o root -g root $(BIN_PATH)/bakatui /usr/bin/
clean: clean:
rm -fr build rm -fr build
.PHONY: all clean make-build-dir .PHONY: all clean install debug
+9
View File
@@ -3,3 +3,12 @@
> ### Dependencies: > ### Dependencies:
> [nlohmann-json](https://github.com/nlohmann/json) > [nlohmann-json](https://github.com/nlohmann/json)
> ncurses
> [!IMPORTANT]
> Only folowing works:
> - [x] Login
> - [x] Marks
> - [x] Timetable
> - [ ] Komens
> - [ ] Absence
+6
View File
@@ -1,3 +1,7 @@
// Header guard
#ifndef RESET
#define RESET "\033[0m" #define RESET "\033[0m"
#define BLACK "\033[30m" /* Black */ #define BLACK "\033[30m" /* Black */
#define RED "\033[31m" /* Red */ #define RED "\033[31m" /* Red */
@@ -15,3 +19,5 @@
#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */ #define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */
#define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */ #define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */
#define BOLDWHITE "\033[1m\033[37m" /* Bold White */ #define BOLDWHITE "\033[1m\033[37m" /* Bold White */
#endif
+19
View File
@@ -0,0 +1,19 @@
#include <string_view>
#ifndef VERSION
#define VERSION "0.7.2"
#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
+24
View File
@@ -0,0 +1,24 @@
#include "const.h"
#include <iostream>
#include "helper_funcs.h"
#include <cstdio>
void PrintHelp() {
std::cout << "Usage: " << NAME << " [OPTIONS]" << "\n"
<< "-h Show this help menu\n"
<< "-V Show version\n"
<< "-v verbose mode\n"
<< "-L Force new login\n"
<< "-S Ignore SSL cert validity\n";
safe_exit(0);
}
void PrintVersion() {
std::cout << NAME" " << VERSION"\n" << "License GPLv3: GNU GPL version 3 <https://gnu.org/licenses/gpl.html>.\n";
safe_exit(0);
}
void DeleteLogin(std::string savedir_path) {
std::remove((savedir_path + "/authfile").c_str());
std::remove((savedir_path + "/urlfile").c_str());
}
+10
View File
@@ -0,0 +1,10 @@
#include <string>
// header guard
#ifndef _ba_fl_hg_
#define _ba_fl_hg_
void PrintHelp();
void PrintVersion();
void DeleteLogin(std::string savedir_path);
#endif
+137 -23
View File
@@ -1,13 +1,23 @@
#include "helper_funcs.h"
#include "color.h"
#include "main.h"
#include "memory.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 <vector>
std::vector<allocation> *current_allocated;
void safe_exit(int code) { void safe_exit(int code) {
switch (code) { switch (code) {
@@ -26,12 +36,13 @@ void safe_exit(int code) {
case SIGSEGV: case SIGSEGV:
std::cerr << "\nreceived SIGSEGV(segmentaiton fault) exiting...\nIf this " std::cerr << "\nreceived SIGSEGV(segmentaiton fault) exiting...\nIf this "
"repeats please report it as bug\n"; "repeats please report it as a bug\n";
break; break;
default: default:
break; break;
} }
delete_all(current_allocated);
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
endwin(); endwin();
@@ -43,32 +54,44 @@ std::string bool_to_string(bool bool_in) { return bool_in ? "true" : "false"; }
std::string SoRAuthFile(bool save, std::string data) { std::string SoRAuthFile(bool save, std::string data) {
std::string savedir_path = std::getenv("HOME"); std::string home = std::getenv("HOME");
savedir_path.append("/.local/share/bakatui"); if (home.empty()) {
std::cerr << RED "[ERROR] " RESET << "HOME environment variable not set.\n";
DIR *savedir = opendir(savedir_path.c_str()); safe_exit(EXIT_FAILURE);
if (savedir) {
/* Directory exists. */
closedir(savedir);
} else if (ENOENT == errno) {
/* Directory does not exist. */
std::filesystem::create_directories(savedir_path);
} else {
/* opendir() failed for some other reason. */
std::cerr << "cannot access ~/.local/share/bakatui\n";
safe_exit(100);
} }
std::string authfile_path = std::string(savedir_path) + "/auth"; std::string savedir_path = home;
savedir_path.append("/.local/share/bakatui");
if (!std::filesystem::exists(savedir_path)) {
if (!std::filesystem::create_directories(savedir_path)) {
std::cerr << RED "[ERROR] " RESET
<< "Failed to create directory: " << savedir_path << "\n";
safe_exit(EXIT_FAILURE);
}
}
std::string authfile_path = savedir_path + "/auth";
if (save) { if (save) {
std::ofstream authfile(authfile_path); std::ofstream authfile(authfile_path);
if (!authfile.is_open()) {
std::cerr << RED "[ERROR] " RESET
<< "Failed to open auth file for writing.\n";
safe_exit(EXIT_FAILURE);
}
authfile << data; authfile << data;
authfile.close(); authfile.close();
return ""; return "";
} else { } else {
std::ifstream authfile(authfile_path); std::ifstream authfile(authfile_path);
authfile >> data; if (!authfile.is_open()) {
std::cerr << RED "[ERROR] " RESET
<< "Failed to open auth file for reading.\n";
safe_exit(EXIT_FAILURE);
}
data.assign((std::istreambuf_iterator<char>(authfile)),
std::istreambuf_iterator<char>());
authfile.close(); authfile.close();
return data; return data;
} }
@@ -88,8 +111,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;
@@ -112,19 +136,109 @@ 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);
if (mvwaddwstr(win, y, x, string) == ERR) {
if (config.verbose) {
std::wcerr << RED "[ERROR]" << RESET " wprint_in_middle failed to print "
<< string << "\n";
}
}
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);
return (start == std::string::npos) ? "" : s.substr(start); return (start == std::string::npos) ? "" : s.substr(start);
} }
std::string rtrim(const std::string &s) { std::string rtrim(const std::string &s) {
size_t end = s.find_last_not_of(WHITESPACE); size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1); return (end == std::string::npos) ? "" : s.substr(0, end + 1);
} }
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);
} }
+28 -1
View File
@@ -1,9 +1,36 @@
#include "memory.h"
#include <curses.h> #include <curses.h>
#include <string> #include <string>
#include <vector>
// header guard
#ifndef _ba_hf_hg_
#define _ba_hf_hg_
extern std::vector<allocation> *current_allocated;
#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
+20 -5
View File
@@ -2,9 +2,9 @@
#include "color.h" #include "color.h"
#include "helper_funcs.h" #include "helper_funcs.h"
#include "main_menu.h" #include "main_menu.h"
#include "marks.h"
#include "net.h" #include "net.h"
#include <csignal> #include <csignal>
#include <cstdlib>
#include <curl/curl.h> #include <curl/curl.h>
#include <curses.h> #include <curses.h>
#include <fstream> #include <fstream>
@@ -12,9 +12,13 @@
#include <regex> #include <regex>
#include <string> #include <string>
#include <unistd.h> #include <unistd.h>
#include "flags.h"
#include "types.h"
std::string baka_api_url; std::string baka_api_url;
Config config;
int main(int argc, char **argv) { int main(int argc, char **argv) {
// signal handlers // signal handlers
signal(SIGTERM, safe_exit); signal(SIGTERM, safe_exit);
@@ -25,12 +29,23 @@ int main(int argc, char **argv) {
// error signal handlers // error signal handlers
signal(SIGSEGV, safe_exit); signal(SIGSEGV, safe_exit);
marks_page();
/*
{ {
std::string savedir_path = std::getenv("HOME"); std::string savedir_path = std::getenv("HOME");
savedir_path.append("/.local/share/bakatui"); savedir_path.append("/.local/share/bakatui");
int opt;
while ((opt = getopt(argc, argv, "hVvLS:")) != -1) {
switch (opt) {
case 'h': PrintHelp(); break;
case 'V': PrintVersion(); break;
case 'v': config.verbose = true; break;
case 'L': DeleteLogin(savedir_path); break;
case 'S': config.ignoressl = true; break;
default: std::cerr << RED"[ERROR]" << RESET" invalid option: " << (char)optopt << "\ntry: -h\n"; safe_exit(EINVAL);
}
}
std::string urlfile_path = std::string(savedir_path) + "/url"; std::string urlfile_path = std::string(savedir_path) + "/url";
std::ifstream urlfile; std::ifstream urlfile;
urlfile.open(urlfile_path); urlfile.open(urlfile_path);
@@ -64,6 +79,6 @@ int main(int argc, char **argv) {
get_input_and_login(); get_input_and_login();
} }
main_menu(); main_menu();
*/
return 0; return 0;
} }
+7
View File
@@ -1,4 +1,11 @@
#include <curl/curl.h> #include <curl/curl.h>
#include <string> #include <string>
#include "types.h"
// header guard
#ifndef _ba_ma_hg_
#define _ba_ma_hg_
extern std::string baka_api_url; extern std::string baka_api_url;
extern Config config;
#endif
+16 -10
View File
@@ -1,18 +1,22 @@
#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 <menu.h> #include <menu.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #include "marks.h"
#define CTRLD 4 #define CTRLD 4
char *choices[] = { wchar_t *choices[] = {
"login", "Grades", "shedule", "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, timetable_page, nullptr,
nullptr, nullptr, nullptr, nullptr};
void main_menu() { void main_menu() {
ITEM **my_items; ITEM **my_items;
@@ -22,6 +26,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();
@@ -34,7 +39,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);
@@ -54,7 +60,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);
@@ -85,11 +91,11 @@ void main_menu() {
menu_driver(my_menu, REQ_UP_ITEM); menu_driver(my_menu, REQ_UP_ITEM);
break; break;
case 10: // ENTER case 10: // ENTER
move(20, 0); clear();
clrtoeol(); choicesFuncs[item_index(current_item(my_menu))]();
mvprintw(20, 0, "Item selected is : %s",
item_name(current_item(my_menu)));
pos_menu_cursor(my_menu); pos_menu_cursor(my_menu);
refresh();
wrefresh(my_menu_win);
break; break;
} }
wrefresh(my_menu_win); wrefresh(my_menu_win);
+6
View File
@@ -1 +1,7 @@
// header guard
#ifndef _ba_mm_hg_
#define _ba_mm_hg_
void main_menu(); void main_menu();
#endif
+166 -78
View File
@@ -1,43 +1,25 @@
#include "marks.h" #include "marks.h"
#include "helper_funcs.h" #include "helper_funcs.h"
#include "memory.h"
#include "net.h"
#include <algorithm>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <curses.h> #include <curses.h>
#include <fstream> #include <format>
#include <iostream>
#include <menu.h> #include <menu.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <panel.h> #include <panel.h>
#include <string>
#include <vector>
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
@@ -45,23 +27,30 @@ SOFTWARE.
#define DEFAULT_X_OFFSET 10 #define DEFAULT_X_OFFSET 10
#define DEFAULT_Y_OFFSET 2 #define DEFAULT_Y_OFFSET 2
#define DEFAULT_PADDING 4
std::vector<allocation> marks_allocated;
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); void win_show(WINDOW *win, wchar_t *label, int label_color, int width,
int height, json marks_json, int SubjectIndex);
void marks_page() { void marks_page() {
// DONT FORGET TO UNCOMMENT current_allocated = &marks_allocated;
// json resp_from_api = bakaapi::get_grades(); json resp_from_api;
std::ifstream f("test-data/marks2.json"); {
json resp_from_api = json::parse(f); std::string endpoint = "api/3/marks";
resp_from_api = bakaapi::get_data_from_endpoint(endpoint);
WINDOW **my_wins; }
size_t size_my_wins = resp_from_api["Subjects"].size(); size_t size_my_wins = resp_from_api["Subjects"].size();
my_wins = new (std::nothrow) WINDOW *[size_my_wins]; WINDOW **my_wins = new (std::nothrow) WINDOW *[size_my_wins];
marks_allocated.push_back({WINDOW_ARRAY, my_wins, size_my_wins});
PANEL **my_panels;
size_t size_my_panels = resp_from_api["Subjects"].size(); size_t size_my_panels = resp_from_api["Subjects"].size();
my_panels = new (std::nothrow) PANEL *[size_my_panels]; PANEL **my_panels = new (std::nothrow) PANEL *[size_my_panels];
marks_allocated.push_back({PANEL_ARRAY, my_panels, size_my_panels});
// trows compiler warning for some reason but cannot be removed
PANEL *top; PANEL *top;
int ch; int ch;
@@ -73,8 +62,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);
@@ -82,78 +69,133 @@ void marks_page() {
init_wins(my_wins, resp_from_api["Subjects"].size(), resp_from_api); init_wins(my_wins, resp_from_api["Subjects"].size(), resp_from_api);
for (size_t i = 0; i < size_my_panels; i++) { // store all original window position
/* Attach a panel to each window Order is bottom up */ int *original_y = new int[size_my_wins];
my_panels[i] = new_panel(my_wins[i]); marks_allocated.push_back({GENERIC_ARRAY, original_y, size_my_wins});
int *original_x = new int[size_my_wins];
/* Set up the user pointers to the next panel */ marks_allocated.push_back({GENERIC_ARRAY, original_x, size_my_wins});
if ((i + 1) < size_my_panels) { for (size_t i = 0; i < size_my_wins; ++i) {
set_panel_userptr(my_panels[i], my_panels[(i + 1)]); getbegyx(my_wins[i], original_y[i], original_x[i]);
} else {
set_panel_userptr(my_panels[i], my_panels[0]);
}
} }
// Update the stacking order. // Attach panels
update_panels(); for (size_t i = 0; i < size_my_panels; i++) {
my_panels[i] = new_panel(my_wins[i]);
set_panel_userptr(my_panels[i], (i + 1 < size_my_panels) ? my_panels[i + 1]
: my_panels[0]);
}
/* Show it on the screen */ update_panels();
attron(COLOR_PAIR(4)); attron(COLOR_PAIR(4));
mvprintw(LINES - 2, 0, "Use tab to browse through the windows (F1 to Exit)"); mvprintw(LINES - 2, 0, "Arrows/j/k to scroll | F1 to exit | {mark} [weight]");
attroff(COLOR_PAIR(4)); attroff(COLOR_PAIR(4));
doupdate(); doupdate();
top = my_panels[resp_from_api["Subjects"].size() - 1]; top = my_panels[size_my_panels - 1];
long y_offset = 0;
// Main loop
while ((ch = getch()) != KEY_F(1)) { while ((ch = getch()) != KEY_F(1)) {
bool needs_update = false;
switch (ch) { switch (ch) {
case 9: case KEY_UP:
top = (PANEL *)panel_userptr(top); case 'k': // Vim-style up
top_panel(top); y_offset--;
needs_update = true;
break;
case KEY_DOWN:
case 'j': // Vim-style down
y_offset++;
needs_update = true;
break; break;
} }
update_panels();
doupdate(); // Update window positions if scrolled
if (needs_update) {
for (size_t i = 0; i < size_my_panels; ++i) {
int new_y = original_y[i] - y_offset;
int new_x = original_x[i];
move_panel(my_panels[i], new_y, new_x);
}
update_panels();
doupdate();
}
} }
// Cleanup
endwin(); endwin();
delete[] my_wins; clear();
delete[] my_panels; delete_all(&marks_allocated);
} }
/* 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;
unsigned int MaxHight = 0;
// 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"];
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
for (int j = 0; j < marks_json["Subjects"][i]["Marks"].size(); j++) { std::wstring wsub_name = string_to_wstring(sub_name);
std::string caption = marks_json["Subjects"][i]["Marks"][j]["Caption"]; std::wstring wsub_avg_s = string_to_wstring(sub_avg_s);
std::string theme = marks_json["Subjects"][i]["Marks"][j]["Theme"];
caption = rm_tr_le_whitespace(caption); // Using swprintf for wide character formatting
theme = rm_tr_le_whitespace(theme); 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 (unsigned int j = 0; j < static_cast<unsigned int>(
marks_json["Subjects"][i]["Marks"].size());
j++) {
std::string caption =
rm_tr_le_whitespace(marks_json["Subjects"][i]["Marks"][j]["Caption"]);
std::string 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 + 4; 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 += NLINES + 10; y += MaxHight + 2;
MaxHight = 0;
}
if (static_cast<unsigned int>(marks_json["Subjects"][i]["Marks"].size()) *
2 +
DEFAULT_PADDING >
MaxHight) {
MaxHight =
marks_json["Subjects"][i]["Marks"].size() * 2 + DEFAULT_PADDING;
} }
wins[i] = newwin(NLINES, NCOLS, y, x); wins[i] = newwin(NLINES, NCOLS, y, x);
win_show(wins[i], label, curent_color + 1, width); win_show(wins[i], label, curent_color + 1, width,
marks_json["Subjects"][i]["Marks"].size() * 2 + DEFAULT_PADDING,
marks_json, i);
curent_color = (curent_color + 1) % 7; curent_color = (curent_color + 1) % 7;
x += width + 5; x += width + 5;
@@ -161,9 +203,12 @@ 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) { void win_show(WINDOW *win, wchar_t *label, int label_color, int width,
int startx, starty, height; int height, json marks_json, int SubjectIndex) {
height = 20;
// is the compiler smoking weed or something, why is it thinking starty is not
// used ??
int startx, starty;
wresize(win, height, width); wresize(win, height, width);
@@ -175,6 +220,49 @@ void win_show(WINDOW *win, char *label, int label_color, int width) {
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));
print_in_middle(win, 1, 0, width, label, COLOR_PAIR(label_color));
wchar_t CaptionBuf[1500];
wchar_t ThemeBuf[1500];
std::wstring wCaption;
int AdditionalOffset = 0;
for (size_t i = 0; i < marks_json["Subjects"][SubjectIndex]["Marks"].size();
i++) {
std::string Caption =
marks_json["Subjects"][SubjectIndex]["Marks"][i]["Caption"];
Caption = rm_tr_le_whitespace(Caption);
std::string MarkText =
marks_json["Subjects"][SubjectIndex]["Marks"][i]["MarkText"];
int Weight = marks_json["Subjects"][SubjectIndex]["Marks"][i]["Weight"];
// Create formatted string with mark and weight
std::string formattedCaption =
Caption + std::format(" - {{{}}} [{}]", MarkText, Weight);
// Convert to wide string
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));
AdditionalOffset++;
}
} }
+4
View File
@@ -1 +1,5 @@
// header guard
#ifndef _ba_mp_hg_
#define _ba_mp_hg_
void marks_page(); void marks_page();
#endif
+48
View File
@@ -0,0 +1,48 @@
#include "memory.h"
#include "color.h"
#include <iostream>
#include <ncurses.h>
#include <panel.h>
void delete_all(std::vector<allocation> *allocated) {
if (allocated == nullptr) {
return;
}
for (long long i = allocated->size() - 1; i >= 0; i--) {
switch (allocated->at(i).type) {
case WINDOW_ARRAY: {
WINDOW **windows = static_cast<WINDOW **>(allocated->at(i).ptr);
for (std::size_t j = 0; j < allocated->at(i).size; j++) {
delwin(windows[j]);
}
delete[] windows;
break;
}
case PANEL_ARRAY: {
PANEL **panels = static_cast<PANEL **>(allocated->at(i).ptr);
for (std::size_t j = 0; j < allocated->at(i).size; j++) {
del_panel(panels[j]);
}
delete[] panels;
break;
}
case GENERIC_ARRAY:
delete[] static_cast<char *>(allocated->at(i).ptr);
break;
case WINDOW_TYPE:
delwin(static_cast<WINDOW *>(allocated->at(i).ptr));
break;
case PANEL_TYPE:
del_panel(static_cast<PANEL *>(allocated->at(i).ptr));
break;
case GENERIC_TYPE:
delete static_cast<char *>(allocated->at(i).ptr);
break;
default:
std::cerr << RED "[!!CRITICAL!!]" << RESET " Unknown allocation type"
<< "\n";
break;
}
allocated->pop_back();
}
}
+23
View File
@@ -0,0 +1,23 @@
// header guard
#ifndef _ba_me_hg_
#define _ba_me_hg_
#include <cstddef>
#include <vector>
enum AllocationType {
WINDOW_ARRAY,
PANEL_ARRAY,
GENERIC_ARRAY,
WINDOW_TYPE,
PANEL_TYPE,
GENERIC_TYPE
};
struct allocation {
AllocationType type;
void *ptr;
std::size_t size;
};
void delete_all(std::vector<allocation> *allocated);
#endif
+57 -26
View File
@@ -1,7 +1,9 @@
#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 <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
#include <cstdlib> #include <cstdlib>
@@ -18,6 +20,14 @@
using nlohmann::json; using nlohmann::json;
// metods
enum metod {
GET,
POST,
};
std::string access_token;
CURL *curl = curl_easy_init(); CURL *curl = curl_easy_init();
// Callback function to write data into a std::string // Callback function to write data into a std::string
@@ -28,30 +38,45 @@ 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> send_curl_request(std::string endpoint, metod type,
std::string 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) {
url.append("?" + req_data);
}
if (curl) { if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
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);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); if (config.ignoressl) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req_data.c_str()); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
struct curl_slist *headers = NULL; struct curl_slist *headers = NULL;
headers = curl_slist_append( headers = curl_slist_append(
headers, "Content-Type: application/x-www-form-urlencoded"); 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);
if (type == "POST") { switch (type) {
case GET:
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
break;
case POST:
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req_data.c_str());
curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POST, 1L);
break;
default:
std::cerr << RED "[ERROR] " << RESET "invalid metod\n";
safe_exit(EINVAL);
} }
} else { } else {
@@ -63,21 +88,17 @@ 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);
curl_easy_cleanup(curl); // Cleanup
return {response, http_code}; return {response, http_code};
} }
namespace bakaapi { namespace bakaapi {
std::string access_token;
void login(std::string username, std::string password) { void login(std::string username, std::string password) {
std::string req_data = std::string req_data =
std::format("client_id=ANDR&grant_type=password&username={}&password={}", std::format("client_id=ANDR&grant_type=password&username={}&password={}",
username, password); username, password);
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 << "login failed " << http_code std::cerr << RED "[ERROR] " << RESET << "login failed " << http_code
<< " is non 200 response\n"; << " is non 200 response\n";
@@ -100,12 +121,12 @@ void login(std::string username, std::string password) {
json resp_parsed = json::parse(response); json resp_parsed = json::parse(response);
access_token = resp_parsed["access_token"]; access_token = resp_parsed["access_token"];
// DEBUG
std::cout << "access token: " << access_token << std::endl;
} }
void refresh_access_token() { void refresh_access_token() {
if (config.verbose) {
std::clog << "refreshing access token please wait...\n";
}
json authfile_parsed = json::parse(SoRAuthFile(false, "")); json authfile_parsed = json::parse(SoRAuthFile(false, ""));
@@ -116,7 +137,7 @@ void refresh_access_token() {
"token&refresh_token={}", "token&refresh_token={}",
refresh_token); refresh_token);
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
<< "is non 200 response\n"; << "is non 200 response\n";
@@ -130,21 +151,31 @@ void refresh_access_token() {
access_token = resp_parsed["access_token"]; access_token = resp_parsed["access_token"];
} }
json get_grades() { 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"];
} }
std::string req_data = }
std::format("Authorization=Bearer&access_token={}",
access_token);
auto [response, http_code] = send_curl_request("api/3/marks", "GET", req_data); // supports all endpoints that only require access_token
json get_data_from_endpoint(std::string &endpoint,
std::string additional_data) {
is_access_token_empty();
access_token_refreshed:
std::string req_data =
std::format("Authorization=Bearer&access_token={}", access_token);
if (!additional_data.empty()) {
req_data.append(std::format("&{}", additional_data));
}
if(http_code != 200) { auto [response, http_code] = send_curl_request(endpoint, GET, req_data);
refresh_access_token();
}
return json::parse(response); if (http_code != 200) {
refresh_access_token();
goto access_token_refreshed;
}
return json::parse(response);
} }
} // namespace bakaapi } // namespace bakaapi
+5 -1
View File
@@ -1,6 +1,9 @@
#include <curl/curl.h> #include <curl/curl.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <string> #include <string>
// header guard
#ifndef _ba_ne_hg_
#define _ba_ne_hg_
using nlohmann::json; using nlohmann::json;
@@ -8,5 +11,6 @@ extern CURL *curl;
namespace bakaapi { namespace bakaapi {
void login(std::string username, std::string password); void login(std::string username, std::string password);
void refresh_access_token(); void refresh_access_token();
json get_grades(); json get_data_from_endpoint(std::string &endpoint, std::string data = "");
} // namespace bakaapi } // namespace bakaapi
#endif
+679
View File
@@ -0,0 +1,679 @@
#include "timetable.h"
#include "color.h"
#include "const.h"
#include "helper_funcs.h"
#include "memory.h"
#include "net.h"
#include "types.h"
#include <bits/chrono.h>
#include <cstdint>
#include <cstdio>
#include <ctime>
#include <curses.h>
#include <cwchar>
#include <iomanip>
#include <iostream>
#include <ncurses.h>
#include <nlohmann/json.hpp>
#include <panel.h>
#include <sstream>
#include <string>
#include <sys/types.h>
#include <vector>
using nlohmann::json;
#define BOTTOM_PADDING 5
#define DEFAULT_OFFSET 3
std::vector<allocation> timetable_allocated;
const wchar_t *day_abriviations[] = {nullptr, L"Mo", L"Tu", L"We",
L"Th", L"Fr", L"Sa", L"Su"};
void draw_days(WINDOW **&day_windows, uint16_t cell_height, uint8_t num_of_days,
json &resp_from_api);
void draw_lessons(WINDOW **&lesson_windows, uint8_t num_of_columns,
uint16_t cell_width, std::vector<uint8_t> &HourIdLookupTable,
json &resp_from_api);
void draw_cells(uint8_t num_of_columns, uint8_t num_of_days,
uint16_t cell_width, uint16_t cell_height,
std::vector<std::vector<WINDOW *>> &cells,
std::vector<uint8_t> &HourIdLookupTable, json &resp_from_api);
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>());
}
json *find_atom_by_indexes(json &resp_from_api, uint8_t day_index,
uint8_t hour_index,
const std::vector<uint8_t> &HourIdLookupTable) {
for (uint8_t k = 0; k < resp_from_api["Days"][day_index]["Atoms"].size();
k++) {
if (resp_from_api["Days"][day_index]["Atoms"][k]["HourId"].get<uint8_t>() ==
HourIdLookupTable[hour_index]) {
return &resp_from_api["Days"][day_index]["Atoms"][k];
}
}
return nullptr; // No matching atom found
}
void timetable_page() {
current_allocated = &timetable_allocated;
auto dateSelected = std::chrono::system_clock::now();
reload_for_new_week:
std::time_t date_time_t = std::chrono::system_clock::to_time_t(dateSelected);
std::tm local_time = *std::localtime(&date_time_t);
std::stringstream date_stringstream;
date_stringstream << std::put_time(&local_time, "%Y-%m-%d");
std::string date_string = "date=" + date_stringstream.str();
std::string endpoint = "api/3/timetable/actual";
json resp_from_api = bakaapi::get_data_from_endpoint(endpoint, date_string);
// 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
Id_and_Start_time *temp_hour_sorting_array =
new Id_and_Start_time[resp_from_api["Hours"].size()];
timetable_allocated.push_back({GENERIC_ARRAY, temp_hour_sorting_array,
resp_from_api["Hours"].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;
timetable_allocated.pop_back();
}
// 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];
timetable_allocated.push_back({WINDOW_ARRAY, day_windows, num_of_days});
WINDOW **lesson_windows = new WINDOW *[num_of_columns];
timetable_allocated.push_back({WINDOW_ARRAY, lesson_windows, num_of_columns});
std::vector<std::vector<WINDOW *>> cells(
num_of_days, std::vector<WINDOW *>(num_of_columns));
// init day windows
for (uint8_t i = 0; i < num_of_days; i++) {
day_windows[i] = newwin(cell_height, DEFAULT_OFFSET,
i * cell_height + DEFAULT_OFFSET, 0);
}
// init cell windows
for (uint8_t i = 0; i < num_of_columns; i++) {
lesson_windows[i] =
newwin(DEFAULT_OFFSET, cell_width, 0, i * cell_width + DEFAULT_OFFSET);
}
draw_lessons(lesson_windows, num_of_columns, cell_width, HourIdLookupTable,
resp_from_api);
// days have to be drawn after lessons for some reason i actualy have no idea
// why
draw_days(day_windows, cell_height, num_of_days, resp_from_api);
// init the cell windows
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);
}
}
draw_cells(num_of_columns, num_of_days, cell_width, cell_height, cells,
HourIdLookupTable, resp_from_api);
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);
timetable_allocated.push_back({WINDOW_TYPE, selector_windows[i], 1});
selector_panels[i] = new_panel(selector_windows[i]);
timetable_allocated.push_back({PANEL_TYPE, selector_panels[i], 1});
wattron(selector_windows[i], COLOR_PAIR(COLOR_RED));
mvwaddch(selector_windows[i], 0, 0, corners[i]);
wattroff(selector_windows[i], COLOR_PAIR(COLOR_RED));
}
}
attron(COLOR_PAIR(COLOR_BLUE));
mvprintw(LINES - 2, 0,
"Arrows/hjkl to select | ENTER to show info | p/n to select weeks "
"|F1 to exit");
{
std::tm end_week = local_time;
std::tm start_week = local_time;
// Convert tm_wday (0-6) to API day format (1-7)
int current_wday = (local_time.tm_wday == 0) ? 7 : local_time.tm_wday;
// Get days of week from API (1-7 format where Monday is 1)
uint8_t start_day = resp_from_api["Days"][0]["DayOfWeek"].get<uint8_t>();
uint8_t end_day =
resp_from_api["Days"][resp_from_api["Days"].size() - 1]["DayOfWeek"]
.get<uint8_t>();
// Calculate days back to start day (handles week wraparound)
int days_back = (current_wday >= start_day)
? (current_wday - start_day)
: (current_wday + 7 - start_day);
// Calculate days forward to end day (handles week wraparound)
int days_forward = (current_wday <= end_day) ? (end_day - current_wday)
: (end_day + 7 - current_wday);
// Adjust dates
start_week.tm_mday -= days_back;
end_week.tm_mday += days_forward;
// Normalize the dates
std::mktime(&start_week);
std::mktime(&end_week);
// Format the dates as strings
std::stringstream start_week_strstream, end_week_strstream;
start_week_strstream << std::put_time(&start_week, "%d.%m.%Y");
end_week_strstream << std::put_time(&end_week, "%d.%m.%Y");
// kern. developer approved ↓↓
mvprintw(LINES - 2,
COLS - (start_week_strstream.str().length() + 3 +
end_week_strstream.str().length()),
"%s",
(start_week_strstream.str() + " - " + end_week_strstream.str())
.c_str());
}
attroff(COLOR_PAIR(COLOR_BLUE));
update_panels();
doupdate();
WINDOW *infobox_window;
PANEL *infobox_panel;
bool is_info_box_open = false;
int ch;
while ((ch = getch()) != KEY_F(1)) {
if (is_info_box_open) {
hide_panel(infobox_panel);
del_panel(infobox_panel);
delwin(infobox_window);
touchwin(stdscr);
refresh();
// Redraw everithing
draw_days(day_windows, cell_height, num_of_days, resp_from_api);
draw_lessons(lesson_windows, num_of_columns, cell_width,
HourIdLookupTable, resp_from_api);
draw_cells(num_of_columns, num_of_days, cell_width, cell_height, cells,
HourIdLookupTable, resp_from_api);
for (uint8_t i = 0; i < selector_panels.size(); i++) {
top_panel(selector_panels[i]);
}
update_panels();
doupdate();
is_info_box_open = false;
continue;
}
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;
case 'p':
case 'n':
(ch == 'p') ? dateSelected = dateSelected - std::chrono::days(7)
: dateSelected = dateSelected + std::chrono::days(7);
delete_all(&timetable_allocated);
goto reload_for_new_week;
break;
case 10: // ENTER
json *atom = find_atom_by_indexes(resp_from_api, selected_cell.y,
selected_cell.x, HourIdLookupTable);
if (atom == nullptr) {
std::cerr << RED "[ERROR]" << RESET " Selector at invalid position\n";
safe_exit(129);
}
infobox_window = newwin(LINES * 0.6, COLS * 0.6, LINES * 0.2, COLS * 0.2);
infobox_panel = new_panel(infobox_window);
is_info_box_open = true;
wattron(infobox_window, COLOR_PAIR(COLOR_MAGENTA));
box(infobox_window, 0, 0);
mvwaddch(infobox_window, 2, 0, ACS_LTEE);
mvwhline(infobox_window, 2, 1, ACS_HLINE, COLS * 0.6 - 2);
mvwaddch(infobox_window, 2, COLS * 0.6 - 1, ACS_RTEE);
wattroff(infobox_window, COLOR_PAIR(COLOR_MAGENTA));
std::wstring Caption;
if (atom->contains("Change") && !atom->at("Change").is_null()) {
if (!atom->at("Change")["TypeName"].is_null()) {
Caption = string_to_wstring(
atom->at("Change")["TypeName"].get<std::string>());
}
}
if (Caption.empty()) {
try {
Caption = get_data_for_atom(resp_from_api, atom, "Subjects",
"SubjectId", "Name");
} catch (...) {
__asm__("nop");
}
}
std::wstring Teacher = L"";
try {
Teacher = get_data_for_atom(resp_from_api, atom, "Teachers",
"TeacherId", "Name");
} catch (...) {
__asm__("nop");
}
Teacher.insert(0, L"Teacher: ");
std::wstring Groups = L"";
try {
for (uint8_t i = 0; i < atom->at("GroupIds").size(); i++) {
for (uint8_t j = 0; j < resp_from_api["Groups"].size(); j++) {
if (resp_from_api["Groups"][j]["Id"].get<std::string>() ==
atom->at("GroupIds")[i].get<std::string>()) {
Groups.append(string_to_wstring(
resp_from_api["Groups"][j]["Name"].get<std::string>()));
if (static_cast<size_t>(i + 1) < atom->at("GroupIds").size()) {
Groups.append(L", ");
}
}
}
}
} catch (const std::exception &e) {
std::cerr << RED "[ERROR]" << RESET " " << e.what() << "\n";
}
Groups = wrm_tr_le_whitespace(Groups);
Groups.insert(0, L"Groups: ");
std::wstring Room = L"";
try {
Room =
get_data_for_atom(resp_from_api, atom, "Rooms", "RoomId", "Name");
if (Room.empty()) {
Room = get_data_for_atom(resp_from_api, atom, "Rooms", "RoomId",
"Abbrev");
;
}
} catch (...) {
__asm__("nop");
}
Room.insert(0, L"Room: ");
std::wstring Theme = L"";
try {
Theme = wrm_tr_le_whitespace(
string_to_wstring(atom->at("Theme").get<std::string>()));
} catch (...) {
__asm__("nop");
}
Theme.insert(0, L"Theme: ");
wprint_in_middle(infobox_window, 1, 0, getmaxx(infobox_window),
Caption.c_str(), COLOR_PAIR(COLOR_CYAN));
// printing out of order to reduce wattro* directives
wattron(infobox_window, COLOR_PAIR(COLOR_YELLOW));
mvwaddwstr(infobox_window, 3, 1, Teacher.c_str());
mvwaddwstr(infobox_window, 5, 1, Room.c_str());
wattroff(infobox_window, COLOR_PAIR(COLOR_YELLOW));
wattron(infobox_window, COLOR_PAIR(COLOR_CYAN));
mvwaddwstr(infobox_window, 4, 1, Groups.c_str());
mvwaddwstr(infobox_window, 6, 1, Theme.c_str());
wattroff(infobox_window, COLOR_PAIR(COLOR_CYAN));
wattron(infobox_window, COLOR_PAIR(COLOR_BLUE));
mvwaddstr(infobox_window, getmaxy(infobox_window) - 2, 1,
"Press any key to close");
wattroff(infobox_window, COLOR_PAIR(COLOR_BLUE));
top_panel(infobox_panel);
update_panels();
doupdate();
continue;
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);
}
draw_cells(num_of_columns, num_of_days, cell_width, cell_height, cells,
HourIdLookupTable, resp_from_api);
update_panels();
doupdate();
} else {
// skip if the cell is empty
goto run_loop_again;
}
}
}
delete_all(&timetable_allocated);
clear();
endwin();
}
void draw_days(WINDOW **&day_windows, uint16_t cell_height, uint8_t num_of_days,
json &resp_from_api) {
for (uint8_t i = 0; i < num_of_days; i++) {
// 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);
mvwaddwstr(
day_windows[i], cell_height / 2, 0,
day_abriviations[resp_from_api["Days"][i]["DayOfWeek"].get<uint8_t>()]);
wrefresh(day_windows[i]);
}
}
void draw_lessons(WINDOW **&lesson_windows, uint8_t num_of_columns,
uint16_t cell_width, std::vector<uint8_t> &HourIdLookupTable,
json &resp_from_api) {
for (uint8_t i = 0; i < num_of_columns; i++) {
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]) {
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, 0, cell_width, caption.c_str(),
COLOR_PAIR(0));
mvwaddwstr(lesson_windows[i], 1, 1, start_time.c_str());
print_in_middle(lesson_windows[i], 1, 0, cell_width, "-", COLOR_PAIR(0));
mvwaddwstr(lesson_windows[i], 1, cell_width - end_time.length() - 1,
end_time.c_str());
wrefresh(lesson_windows[i]);
}
}
void draw_cells(uint8_t num_of_columns, uint8_t num_of_days,
uint16_t cell_width, uint16_t cell_height,
std::vector<std::vector<WINDOW *>> &cells,
std::vector<uint8_t> &HourIdLookupTable, json &resp_from_api) {
for (uint8_t i = 0; i < num_of_days; i++) {
for (uint8_t j = 0; j < num_of_columns; j++) {
json *atom = find_atom_by_indexes(resp_from_api, i, j, HourIdLookupTable);
if (atom == nullptr) {
continue;
}
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, 0, cell_width,
Subject_Abbrev.c_str(), COLOR_PAIR(0));
mvwaddwstr(cells[i][j], cell_height - 2,
cell_width - wcslen(Room_Abbrev.c_str()) - 1,
Room_Abbrev.c_str());
mvwaddwstr(cells[i][j], cell_height - 2, 1, Teacher_Abbrev.c_str());
wrefresh(cells[i][j]);
} catch (const std::exception &e) {
std::cerr << RED "[ERROR]" << RESET " " << e.what() << "\n";
// world's best error handling
__asm__("nop");
}
}
}
}
+6
View File
@@ -0,0 +1,6 @@
// header guard
#ifndef _ba_ti_hg_
#define _ba_ti_hg_
void timetable_page();
#endif
+147
View File
@@ -0,0 +1,147 @@
// header guard
#ifndef _ba_ty_hg_
#define _ba_ty_hg_
#include <istream>
struct Config {
bool verbose = 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