Compare commits

...

81 Commits
v0.1 ... master

Author SHA1 Message Date
15b0344dc8 hide cursor on marks page
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-06-18 17:32:46 +02:00
73fed3df0e version 0.8.2
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-05-24 12:29:19 +02:00
6b3d47ceed add color help for timetable
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-05-24 12:27:41 +02:00
89c67035ca fix menu disappearing after exiting any page 2025-05-24 10:22:07 +02:00
0486d9fb22 fix space before operator warning 2025-05-24 10:18:59 +02:00
e51088eb26 add download progress bar
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-04-14 20:03:48 +02:00
ad6f748899 add homework checkbox to README
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-04-14 07:26:06 +00:00
1d0d4e2362 update README to reflect new aditions
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-04-14 09:24:23 +02:00
a35242b420 fix attachment window not disapearing 2025-04-14 09:22:39 +02:00
e78c8d7efa version 0.8.1
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-04-14 09:12:25 +02:00
f01422eaf5 fix memory leaks
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-04-14 09:10:56 +02:00
9b8bdbf850 add asan build rule to Makefile 2025-04-14 09:10:37 +02:00
c9aef90130 add beter regex to gitignore 2025-04-14 09:10:18 +02:00
d9f545ed09 remove random C89 apperence 2025-04-13 19:31:32 +02:00
d533b526bf fix timetable const issue 2025-04-13 14:51:25 +02:00
ea083e31fc make more timetable variables const 2025-04-13 14:46:50 +02:00
05c406ed6a extract komens usage message to function 2025-04-13 14:44:45 +02:00
a78f4536b2 add komens attachments downloading functionality
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-04-13 12:48:12 +02:00
1992b0e166 add attachment view 2025-04-12 16:00:53 +02:00
575101780d version 0.8
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-04-11 12:58:35 +02:00
48b4f9cda8 fix endpoints & typos
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-04-10 00:19:14 +02:00
5173a05a09 implement send messages and notiseboard 2025-04-10 00:11:55 +02:00
fe9735fe58 add main komes reading
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-04-09 23:43:00 +02:00
e4d67c2163 fix triming of last character in komens
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-04-07 13:02:08 +02:00
c61a354be8 Revert "attempt at puting komes on multiple lines"
Some checks are pending
/ sync-to-origin (push) Waiting to run
This reverts commit 50ef8d9fda39f6c0ecedde3e34665da1e027012c.
2025-04-06 16:32:13 +02:00
50ef8d9fda attempt at puting komes on multiple lines
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-04-06 16:08:54 +02:00
0a3bbffb52 komens list works(sort of) 2025-04-06 15:41:11 +02:00
b352daabfb bit more work on komens
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-04-05 11:49:01 +02:00
b005631d20 add komens skeleton
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-04-04 18:01:50 +02:00
74dc204f22 add vim keys in menu
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-04-01 13:18:02 +02:00
52bea2e3c5 fix SEGFAULT when nullptr
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-04-01 09:32:38 +02:00
22333b6593 version 0.7.1
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-04-01 09:05:28 +02:00
096b1d1eb6 add memory safety
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-31 20:26:56 +02:00
dfa0774242 fix type 2025-03-31 19:33:24 +02:00
24aa979a69 add week selection
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-31 18:20:58 +02:00
64b4797908 make timetable request specific date 2025-03-31 11:27:48 +02:00
522b6fa517 remove useless print statement
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-03-13 11:25:24 +01:00
bee49ee7dc add help text
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-12 17:26:52 +01:00
accc2a79bc add install rule to Makefile 2025-03-12 17:07:50 +01:00
6e573ab992 version 0.7
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-03-12 16:58:11 +01:00
941bbcdb5b make better debug build rule 2025-03-12 16:57:35 +01:00
925b42ceb7 fix days drawing bug 2025-03-12 16:56:32 +01:00
4c0abe5464 Update README.md
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-12 14:07:35 +00:00
e4670f1b26 remove unnessery comments
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-12 15:04:11 +01:00
58e9428299 fix improper function usage 2025-03-12 14:19:45 +01:00
5fc24393c5 fix posible division by zero 2025-03-12 10:33:50 +01:00
04f0ef76ee fix all real warnings 2025-03-11 11:36:23 +01:00
17e850e6d7 add infobox but it contains a BUG
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-03-09 16:33:19 +01:00
01cf082351 Merge branch 'master' of https://git.pupes.org/PoliEcho/bakatui
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-09 09:48:34 +01:00
5088b273b6 fixed timetable selector rendering bug 2025-03-09 09:46:45 +01:00
17f4316947 Update README.md
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-09 08:23:12 +00:00
43d53629ca add selector to timetable. --CONTAINS BUGS--
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-03-09 09:07:54 +01:00
d9fd2d40eb turn HourIdLookupTable into std::vector 2025-03-08 16:06:14 +01:00
19c45c6969 timetable ui done
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-08 15:40:44 +01:00
5c345fb5a1 basic timetable implemented
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-08 13:16:11 +01:00
5782e69313 basic timetable ui 2025-03-08 09:13:17 +01:00
ae3485a899 remove junk 2025-03-07 15:29:20 +01:00
6e9fcd093f add const to print_in_middle functions 2025-03-07 14:00:46 +01:00
d1e34a5c10 add ignoressl toggling
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-03-06 19:45:18 +01:00
3a78917e10 link ncursesw 2025-03-06 19:42:05 +01:00
a407d8c16a wide string refactor 2025-03-06 19:41:30 +01:00
af5252f732 add basic timetable structure 2025-03-06 18:54:51 +01:00
87c9b1e125 add timetable menu entery 2025-03-06 18:54:37 +01:00
41316160ad fix .PHONY 2025-03-06 15:41:13 +01:00
2f639b924a update Makefile
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-06 15:35:59 +01:00
a6ff11ff71 add basic timetable
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-06 15:10:31 +01:00
87c2e2e01c remove useless includes 2025-03-06 15:09:53 +01:00
98b6b8df7e add flag handling and header guards 2025-03-06 14:12:10 +01:00
0d11dbec32 tiny refactor 2025-03-06 12:00:26 +01:00
4f852161aa add more explenation text to marks 2025-03-04 12:33:16 +01:00
50447ac8cd add warning to README
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-03-04 10:32:29 +01:00
d81876f527 remove DEBUG
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-04 10:26:46 +01:00
05d3819d56 fix marks page
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-03-04 10:24:44 +01:00
6985b21d37 fixed 1 segfault
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-01-29 14:55:22 +01:00
4ba8710a71 start of network implementation
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-01-27 16:36:12 +01:00
bd01b506f0 add basic scroling 2025-01-27 10:27:16 +01:00
4c383fce4c add mark Themes and variable window hight
Some checks are pending
/ sync-to-origin (push) Waiting to run
2025-01-26 18:07:50 +01:00
86b9ea812d remove unnessesery line of code 2025-01-26 14:53:21 +01:00
0119429514 fix window overflowing off screen & refactor init_wins() a bit
Some checks failed
/ sync-to-origin (push) Has been cancelled
2025-01-26 14:50:05 +01:00
025c51c3ad first attempt at fixing window sowing outside of screen at marks page 2025-01-26 14:01:36 +01:00
1a882baab6 minor changes : )
Some checks failed
/ sync-to-origin (push) Has been cancelled
2024-12-05 13:52:31 +01:00
26 changed files with 2248 additions and 205 deletions

5
.gitignore vendored
View File

@ -1,6 +1,7 @@
.cache
.vscode
compile_commands.json
core*
*core*
build
test-data
test-data
log

View File

@ -1,8 +1,8 @@
# Compiler and flags
CPPC = g++
CPPC_FLAGS = -std=c++23 -s -O3 -lncurses -lcurl -lmenu -lpanel -Wall -Wextra
# Debug flags:
# CPPC_FLAGS = -ggdb -std=c++23 -lncurses -lcurl -lmenu -lpanel -Wall -Wextra
CPPC_FLAGS = -std=c++23 -s -O3 -lncursesw -lcurl -lmenuw -lpanel -Wall -Wextra -Wno-write-strings
DEBUG_FLAGS = -ggdb -std=c++23 -lncursesw -lcurl -lmenuw -lpanel -Wall -Wextra -Wno-write-strings
DEBUG_ASANITIZE = -fsanitize=address -ggdb -fno-omit-frame-pointer -std=c++23 -lncursesw -lcurl -lmenuw -lpanel -Wall -Wextra -Wno-write-strings
SRC_PATH := src
@ -11,16 +11,21 @@ BIN_PATH := build/bin
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))
all: make-build-dir $(BIN_PATH)/bakatui
debug: CPPC_FLAGS = $(DEBUG_FLAGS)
debug: make-build-dir $(BIN_PATH)/bakatui
asan: CPPC_FLAGS = $(DEBUG_ASANITIZE)
asan: make-build-dir $(BIN_PATH)/bakatui
make-build-dir:
mkdir -p $(OBJ_PATH)
mkdir -p $(OBJ_PATH)/marks
mkdir -p $(BIN_PATH)
@ -28,11 +33,14 @@ $(BIN_PATH)/bakatui: $(OBJ_FILES)
$(CPPC) $(CPPC_FLAGS) $^ -o $@
$(OBJ_PATH)/%.o: $(SRC_PATH)/%.cpp
$(OBJ_PATH)/%.o: $(SRC_PATH)/%.cpp $(SRC_PATH)/%.h
$(CPPC) $(CPPC_FLAGS) -c $< -o $@
install:
@install -vpm 755 -o root -g root $(BIN_PATH)/bakatui /usr/bin/
clean:
rm -fr build
.PHONY: all clean make-build-dir
.PHONY: all clean install debug asan

View File

@ -3,3 +3,13 @@
> ### Dependencies:
> [nlohmann-json](https://github.com/nlohmann/json)
> ncurses
> [!IMPORTANT]
> Only folowing works:
> - [x] Login
> - [x] Marks
> - [x] Timetable
> - [x] Komens
> - [ ] Absence
> - [ ] Homework

View File

@ -1,3 +1,7 @@
// Header guard
#ifndef RESET
#define RESET "\033[0m"
#define BLACK "\033[30m" /* Black */
#define RED "\033[31m" /* Red */
@ -14,4 +18,6 @@
#define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */
#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */
#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
src/const.h Normal file
View File

@ -0,0 +1,19 @@
#include <string_view>
#ifndef VERSION
#define VERSION "0.8.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
src/flags.cpp Normal file
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
src/flags.h Normal file
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

View File

@ -1,13 +1,26 @@
#include "helper_funcs.h"
#include "color.h"
#include "main.h"
#include "memory.h"
#include "net.h"
#include <algorithm>
#include <codecvt>
#include <csignal>
#include <cstring>
#include <curses.h>
#include <dirent.h>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <locale>
#include <panel.h>
#include <regex>
#include <string>
#include <termios.h>
#include <unistd.h>
#include <vector>
std::vector<allocation> *current_allocated;
void safe_exit(int code) {
switch (code) {
@ -26,14 +39,16 @@ void safe_exit(int code) {
case SIGSEGV:
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;
default:
break;
}
delete_all(current_allocated);
curl_easy_cleanup(curl);
endwin();
exit(code);
}
@ -42,32 +57,44 @@ std::string bool_to_string(bool bool_in) { return bool_in ? "true" : "false"; }
std::string SoRAuthFile(bool save, std::string data) {
std::string savedir_path = std::getenv("HOME");
savedir_path.append("/.local/share/bakatui");
DIR *savedir = opendir(savedir_path.c_str());
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 home = std::getenv("HOME");
if (home.empty()) {
std::cerr << RED "[ERROR] " RESET << "HOME environment variable not set.\n";
safe_exit(EXIT_FAILURE);
}
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) {
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.close();
return "";
} else {
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();
return data;
}
@ -87,8 +114,9 @@ void get_input_and_login() {
bakaapi::login(username, password);
}
// Original function
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;
float temp;
@ -109,4 +137,142 @@ void print_in_middle(WINDOW *win, int starty, int startx, int width,
mvwprintw(win, y, x, "%s", string);
wattroff(win, color);
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::wstring WWHITESPACE = L" \n\r\t\f\v";
std::string ltrim(const std::string &s) {
size_t start = s.find_first_not_of(WHITESPACE);
return (start == std::string::npos) ? "" : s.substr(start);
}
std::string rtrim(const std::string &s) {
size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
}
std::string rm_tr_le_whitespace(const std::string &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(const 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];
current_allocated->push_back(allocation{
GENERIC_ARRAY,
dest,
len * MB_CUR_MAX,
});
std::wcstombs(dest, src, len * MB_CUR_MAX);
return dest;
}
wchar_t *char_to_wchar(const 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);
}
std::string html_to_string(std::string html) {
{ // fix new lines
const std::string search = "<br />";
const std::string replace = "\n";
size_t pos = 0;
while ((pos = html.find(search, pos)) != std::string::npos) {
html.replace(pos, search.length(), replace);
pos += replace.length();
}
}
{
std::regex linkPattern("<a\\s+href=[\"'](.*?)[\"'](.*?)>(.*?)</a>");
html = std::regex_replace(html, linkPattern,
"\033]8;;$1\033\\$3\033]8;;\033\\");
}
{
std::regex tag("<[^>]*>");
html = std::regex_replace(html, tag, "");
}
return html;
}

View File

@ -1,8 +1,38 @@
#include "memory.h"
#include <curses.h>
#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);
std::string bool_to_string(bool bool_in);
std::string SoRAuthFile(bool save, std::string data);
void get_input_and_login();
// Original functions
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);
// 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(const wchar_t *src);
wchar_t *char_to_wchar(const char *src);
std::wstring string_to_wstring(const std::string &str);
std::string wstring_to_string(const std::wstring &wstr);
std::string html_to_string(std::string html);
#endif

325
src/komens.cpp Normal file
View File

@ -0,0 +1,325 @@
#include "komens.h"
#include "helper_funcs.h"
#include "memory.h"
#include "net.h"
#include "types.h"
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <curses.h>
#include <cwchar>
#include <future>
#include <menu.h>
#include <ncurses.h>
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
#define MAIN_WIN_PROPORTION 0.714f
#define MAIN_WIN_HIGHT (roundf(LINES * MAIN_WIN_PROPORTION))
#define DEFAULT_OFSET 4
using nlohmann::json;
std::vector<allocation> komens_allocated;
void insert_content(WINDOW *content_win, WINDOW *attachment_win,
const json &resp_from_api);
void komens_print_usage_message() {
attron(COLOR_PAIR(COLOR_BLUE));
mvprintw(LINES - 2, 0,
"Use PageUp and PageDown to scoll down or up a page of items");
mvprintw(LINES - 1, 0, "Arrow Keys to navigate (F1 to Exit)");
attroff(COLOR_PAIR(COLOR_BLUE));
refresh();
}
void komens_page(koment_type type) {
current_allocated = &komens_allocated;
const json resp_from_api = [&]() -> json {
const char *types[] = {"/api/3/komens/messages/received",
"/api/3/komens/messages/sent",
"/api/3/komens/messages/noticeboard"};
const std::string endpoint = types[type];
return bakaapi::get_data_from_endpoint(endpoint, POST);
}();
/* Initialize curses */
setlocale(LC_ALL, "");
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
/* Initialize all the colors */
for (uint8_t i = 0; i < 8; i++) {
init_pair(i, i, COLOR_BLACK);
}
complete_menu komens_choise_menu;
komens_allocated.push_back({COMPLETE_MENU_TYPE, &komens_choise_menu, 1});
size_t num_of_komens = resp_from_api["Messages"].size();
komens_choise_menu.items = new ITEM *[num_of_komens + 1];
komens_choise_menu.items_size = num_of_komens + 1;
char **title_bufs = new char *[num_of_komens];
komens_allocated.push_back({CHAR_PTR_ARRAY, title_bufs, num_of_komens});
char **name_bufs = new char *[num_of_komens];
komens_allocated.push_back({CHAR_PTR_ARRAY, name_bufs, num_of_komens});
size_t max_item_lenght;
{
size_t max_title_lenght = 0;
size_t max_name_lenght = 0;
size_t tmp_lenght;
char tmp_buf[1500];
for (size_t i = 0; i < num_of_komens; i++) {
strlcpy(tmp_buf,
resp_from_api["Messages"][i]["Title"].get<std::string>().c_str(),
sizeof(tmp_buf));
tmp_lenght =
resp_from_api["Messages"][i]["Title"].get<std::string>().length();
if (tmp_lenght > max_title_lenght) {
max_title_lenght = tmp_lenght;
}
title_bufs[i] = new char[strlen(tmp_buf) + 1];
strlcpy(title_bufs[i], tmp_buf, strlen(tmp_buf) + 1);
strlcpy(tmp_buf,
resp_from_api["Messages"][i]["Sender"]["Name"]
.get<std::string>()
.c_str(),
sizeof(tmp_buf));
tmp_lenght = resp_from_api["Messages"][i]["Sender"]["Name"]
.get<std::string>()
.length();
if (tmp_lenght > max_name_lenght) {
max_name_lenght = tmp_lenght;
}
name_bufs[i] = new char[strlen(tmp_buf) + 1];
strlcpy(name_bufs[i], tmp_buf, strlen(tmp_buf) + 1);
komens_choise_menu.items[i] = new_item(title_bufs[i], name_bufs[i]);
}
max_item_lenght = 3 + max_title_lenght + 1 + max_name_lenght;
}
komens_choise_menu.items[num_of_komens] = nullptr;
komens_choise_menu.menu = new_menu(komens_choise_menu.items);
komens_choise_menu.win =
newwin(MAIN_WIN_HIGHT, max_item_lenght + 1, DEFAULT_OFSET, DEFAULT_OFSET);
komens_allocated.push_back({WINDOW_TYPE, komens_choise_menu.win, 1});
set_menu_win(komens_choise_menu.menu, komens_choise_menu.win);
set_menu_sub(komens_choise_menu.menu,
derwin(komens_choise_menu.win, MAIN_WIN_HIGHT - 10,
max_item_lenght, DEFAULT_OFSET - 1, DEFAULT_OFSET - 3));
set_menu_format(komens_choise_menu.menu, MAIN_WIN_HIGHT - 5, 1);
set_menu_mark(komens_choise_menu.menu, " * ");
box(komens_choise_menu.win, 0, 0);
wprint_in_middle(komens_choise_menu.win, 1, 0, max_item_lenght, L"Komens",
COLOR_PAIR(1));
mvwaddch(komens_choise_menu.win, 2, 0, ACS_LTEE);
mvwhline(komens_choise_menu.win, 2, 1, ACS_HLINE, max_item_lenght - 1);
mvwaddch(komens_choise_menu.win, 2, max_item_lenght, ACS_RTEE);
post_menu(komens_choise_menu.menu);
wrefresh(komens_choise_menu.win);
WINDOW *content_win =
newwin(MAIN_WIN_HIGHT, COLS - max_item_lenght - DEFAULT_OFSET - 1,
DEFAULT_OFSET, DEFAULT_OFSET + max_item_lenght + 1);
komens_allocated.push_back({WINDOW_TYPE, content_win, 1});
WINDOW *attachment_win = newwin(1, 1, LINES, COLS);
komens_allocated.push_back({WINDOW_TYPE, attachment_win, 1});
insert_content(content_win, attachment_win,
resp_from_api["Messages"][item_index(
current_item(komens_choise_menu.menu))]);
komens_print_usage_message();
int c;
while ((c = getch()) != KEY_F(1)) {
switch (c) {
case KEY_DOWN:
case KEY_NPAGE:
case 'j':
menu_driver(komens_choise_menu.menu, REQ_DOWN_ITEM);
break;
case KEY_UP:
case KEY_PPAGE:
case 'k':
menu_driver(komens_choise_menu.menu, REQ_UP_ITEM);
break;
default:
if (c >= '1' && c <= '9') {
size_t index = c - '0' - 1;
if (index < resp_from_api["Messages"][item_index(
current_item(komens_choise_menu.menu))]["Attachments"]
.size()) {
std::string default_path =
"~/Downloads/" +
resp_from_api["Messages"][item_index(current_item(
komens_choise_menu.menu))]["Attachments"][index]["Name"]
.get<std::string>();
char path[256];
// Create input prompt at bottom of screen
move(LINES - 1, 0);
clrtoeol();
printw("Save path [%s]: ", default_path.c_str());
echo();
curs_set(1);
getnstr(path, sizeof(path) - 1);
if (strlen(path) == 0)
strcpy(path, default_path.c_str());
noecho();
curs_set(0);
move(LINES - 1, 0);
clrtoeol();
refresh();
char progress_bar[20];
std::fill(progress_bar, progress_bar + 20, '.');
LimitedInt progress_index(0, 0, sizeof(progress_bar) - 1);
// Download the attachment
auto future = std::async(
std::launch::async, bakaapi::download_attachment,
resp_from_api["Messages"][item_index(current_item(
komens_choise_menu.menu))]["Attachments"][index]["Id"]
.get<std::string>(),
path);
while (true) {
if (future.wait_for(std::chrono::seconds(1)) ==
std::future_status::ready) {
// Future has completed
int result = future.get();
if (result != 0) {
attron(COLOR_PAIR(COLOR_RED));
mvprintw(LINES - 1, 0, "Download failed with error code: %d",
result);
attroff(COLOR_PAIR(COLOR_RED));
} else {
attron(COLOR_PAIR(COLOR_GREEN));
mvprintw(LINES - 1, 0, "Download completed successfully");
attroff(COLOR_PAIR(COLOR_GREEN));
}
break;
} else {
progress_bar[progress_index] = '#';
// Future is still running
mvprintw(LINES - 1, 0, "%s", progress_bar);
progress_bar[progress_index] = '.';
progress_index++;
}
}
komens_print_usage_message();
}
}
break;
}
insert_content(content_win, attachment_win,
resp_from_api["Messages"][item_index(
current_item(komens_choise_menu.menu))]);
wrefresh(komens_choise_menu.win);
}
unpost_menu(komens_choise_menu.menu);
endwin();
delete_all(&komens_allocated);
}
void insert_content(WINDOW *content_win, WINDOW *attachment_win,
const json &message) {
wclear(content_win);
mvwprintw(content_win, 0, 0, "%s",
html_to_string(message.at("Text")).c_str());
wrefresh(content_win);
if (!message.at("Attachments").empty()) {
size_t max_item_lenght = 0;
{
size_t max_name_lenght = 0;
size_t max_size_lenght = 0;
size_t tmp_lenght;
for (size_t j = 0; j < message.at("Attachments").size(); j++) {
tmp_lenght =
message.at("Attachments")[j]["Name"].get<std::string>().length();
if (tmp_lenght > max_name_lenght) {
max_name_lenght = tmp_lenght;
}
tmp_lenght =
std::to_string(message.at("Attachments")[j]["Size"].get<size_t>())
.length();
if (tmp_lenght > max_size_lenght) {
max_size_lenght = tmp_lenght;
}
}
max_item_lenght = 3 + max_name_lenght + 1 + max_size_lenght;
}
mvwin(attachment_win, MAIN_WIN_HIGHT + DEFAULT_OFSET + 1,
COLS - max_item_lenght - 2);
wresize(attachment_win, LINES - (MAIN_WIN_HIGHT + DEFAULT_OFSET + 1),
max_item_lenght + 2);
wborder(attachment_win, 0, ' ', 0, 0, 0, ACS_HLINE, 0, ACS_HLINE);
print_in_middle(attachment_win, 0, 0, max_item_lenght + 2, "Attachments",
COLOR_PAIR(COLOR_RED));
for (size_t j = 0; j < message.at("Attachments").size(); j++) {
mvwprintw(
attachment_win, j + 1, 2, "%s %s",
message.at("Attachments")[j]["Name"].get<std::string>().c_str(),
std::to_string(message.at("Attachments")[j]["Size"].get<size_t>())
.c_str());
wattron(attachment_win, COLOR_PAIR(COLOR_MAGENTA));
mvwprintw(attachment_win, j + 1, 0, "%zu>", j + 1);
wattroff(attachment_win, COLOR_PAIR(COLOR_MAGENTA));
}
{ // remove duplicating edges
unsigned short attachment_win_top, attachment_win_left,
attachment_win_height, attachment_win_width;
getbegyx(attachment_win, attachment_win_top, attachment_win_left);
getmaxyx(attachment_win, attachment_win_height, attachment_win_width);
mvvline(attachment_win_top, attachment_win_left - 1, ' ',
attachment_win_height);
}
refresh();
wrefresh(attachment_win);
} else {
// remove attachment window if there are no attachments
wclear(attachment_win);
wrefresh(attachment_win);
}
}

9
src/komens.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef _ba_ko_hg_
#define _ba_ko_hg_
enum koment_type {
RECEIVED,
SEND,
NOTICEBOARD,
};
void komens_page(koment_type type);
#endif

114
src/komens_menu.cpp Normal file
View File

@ -0,0 +1,114 @@
#include "komens_menu.h"
#include "helper_funcs.h"
#include "komens.h"
#include "memory.h"
#include "net.h"
#include "types.h"
#include <cstdlib>
#include <cstring>
#include <curses.h>
#include <menu.h>
std::vector<allocation> komens_menu_allocated;
void komens_menu() {
current_allocated = &komens_menu_allocated;
wchar_t *choices[] = {
L"received", L"sent", L"noticeboard", L"Exit", nullptr,
};
complete_menu komens_menu;
komens_menu_allocated.push_back({COMPLETE_MENU_TYPE, &komens_menu, 1});
int c;
int n_choices;
/* Initialize curses */
setlocale(LC_ALL, "");
initscr();
start_color();
cbreak();
noecho();
keypad(stdscr, TRUE);
init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_CYAN, COLOR_BLACK);
/* Create items */
n_choices = ARRAY_SIZE(choices);
komens_menu.items = new ITEM *[ARRAY_SIZE(choices)];
komens_menu.items_size = ARRAY_SIZE(choices);
for (int i = 0; i < n_choices; ++i)
komens_menu.items[i] =
new_item(wchar_to_char(choices[i]), wchar_to_char(choices[i]));
/* Crate menu */
komens_menu.menu = new_menu(komens_menu.items);
/* Create the window to be associated with the menu */
komens_menu.win = newwin(12, 40, 4, 4);
keypad(komens_menu.win, TRUE);
/* Set main window and sub window */
set_menu_win(komens_menu.menu, komens_menu.win);
set_menu_sub(komens_menu.menu, derwin(komens_menu.win, 8, 38, 3, 1));
set_menu_format(komens_menu.menu, 7, 1);
/* Set menu mark to the string " * " */
set_menu_mark(komens_menu.menu, " * ");
/* Print a border around the main window and print a title */
box(komens_menu.win, 0, 0);
wprint_in_middle(komens_menu.win, 1, 0, 40, L"Komens Menu", COLOR_PAIR(1));
mvwaddch(komens_menu.win, 2, 0, ACS_LTEE);
mvwhline(komens_menu.win, 2, 1, ACS_HLINE, 38);
mvwaddch(komens_menu.win, 2, 39, ACS_RTEE);
/* Post the menu */
post_menu(komens_menu.menu);
wrefresh(komens_menu.win);
attron(COLOR_PAIR(2));
mvprintw(LINES - 2, 0,
"Use PageUp and PageDown to scoll down or up a page of items");
mvprintw(LINES - 1, 0, "Arrow Keys to navigate (F1 to Exit)");
attroff(COLOR_PAIR(2));
refresh();
while ((c = getch()) != KEY_F(1)) {
switch (c) {
case KEY_DOWN:
case KEY_NPAGE:
case 'j':
menu_driver(komens_menu.menu, REQ_DOWN_ITEM);
break;
case KEY_UP:
case KEY_PPAGE:
case 'k':
menu_driver(komens_menu.menu, REQ_UP_ITEM);
break;
case 10: // ENTER
clear();
if (item_index(current_item(komens_menu.menu)) == n_choices - 1) {
goto close_menu;
}
komens_page(
static_cast<koment_type>(item_index(current_item(komens_menu.menu))));
current_allocated = &komens_menu_allocated;
pos_menu_cursor(komens_menu.menu);
refresh();
wrefresh(komens_menu.win);
break;
}
wrefresh(komens_menu.win);
}
close_menu:
/* Unpost and free all the memory taken up */
unpost_menu(komens_menu.menu);
delete_all(&komens_menu_allocated);
endwin();
}

6
src/komens_menu.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef _ba_km_hg_
#define _ba_km_hg_
void komens_menu();
#endif

View File

@ -1,10 +1,12 @@
#include "main.h"
#include "color.h"
#include "flags.h"
#include "helper_funcs.h"
#include "main_menu.h"
#include "marks.h"
#include "net.h"
#include "types.h"
#include <csignal>
#include <cstdlib>
#include <curl/curl.h>
#include <curses.h>
#include <fstream>
@ -15,6 +17,8 @@
std::string baka_api_url;
Config config;
int main(int argc, char **argv) {
// signal handlers
signal(SIGTERM, safe_exit);
@ -25,12 +29,36 @@ int main(int argc, char **argv) {
// error signal handlers
signal(SIGSEGV, safe_exit);
marks_page();
/*
{
std::string savedir_path = std::getenv("HOME");
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::ifstream urlfile;
urlfile.open(urlfile_path);
@ -64,6 +92,6 @@ int main(int argc, char **argv) {
get_input_and_login();
}
main_menu();
*/
return 0;
}

View File

@ -1,4 +1,11 @@
#include <curl/curl.h>
#include <string>
#include "types.h"
// header guard
#ifndef _ba_ma_hg_
#define _ba_ma_hg_
extern std::string baka_api_url;
extern Config config;
#endif

View File

@ -1,27 +1,32 @@
#include "helper_funcs.h"
#include "komens_menu.h"
#include "marks.h"
#include "memory.h"
#include "net.h"
#include "timetable.h"
#include "types.h"
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <curses.h>
#include <menu.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define CTRLD 4
char *choices[] = {
"login", "Grades", "shedule", "Komens",
"Homework", "Absence", "Exit", (char *)NULL,
};
std::vector<allocation> main_menu_allocated;
void main_menu() {
ITEM **my_items;
int c;
MENU *my_menu;
WINDOW *my_menu_win;
int n_choices, i;
current_allocated = &main_menu_allocated;
const wchar_t *choices[] = {
L"login", L"Marks", L"timetable", L"Komens",
L"Homework", L"Absence", L"Exit", nullptr,
};
void (*choicesFuncs[])() = {nullptr, marks_page, timetable_page, komens_menu,
nullptr, nullptr, nullptr, nullptr};
complete_menu main_menu;
main_menu_allocated.push_back({COMPLETE_MENU_TYPE, &main_menu, 1});
/* Initialize curses */
setlocale(LC_ALL, "");
initscr();
start_color();
cbreak();
@ -31,37 +36,39 @@ void main_menu() {
init_pair(2, COLOR_CYAN, COLOR_BLACK);
/* Create items */
n_choices = ARRAY_SIZE(choices);
my_items = (ITEM **)calloc(n_choices, sizeof(ITEM *));
for (i = 0; i < n_choices; ++i)
my_items[i] = new_item(choices[i], choices[i]);
main_menu.items = new ITEM *[ARRAY_SIZE(choices)];
main_menu.items_size = ARRAY_SIZE(choices);
for (size_t i = 0; i < ARRAY_SIZE(choices); ++i) {
main_menu.items[i] =
new_item(wchar_to_char(choices[i]), wchar_to_char(choices[i]));
}
/* Crate menu */
my_menu = new_menu((ITEM **)my_items);
main_menu.menu = new_menu(main_menu.items);
/* Create the window to be associated with the menu */
my_menu_win = newwin(12, 40, 4, 4);
keypad(my_menu_win, TRUE);
main_menu.win = newwin(12, 40, 4, 4);
keypad(main_menu.win, TRUE);
/* Set main window and sub window */
set_menu_win(my_menu, my_menu_win);
set_menu_sub(my_menu, derwin(my_menu_win, 8, 38, 3, 1));
set_menu_format(my_menu, 7, 1);
set_menu_win(main_menu.menu, main_menu.win);
set_menu_sub(main_menu.menu, derwin(main_menu.win, 8, 38, 3, 1));
set_menu_format(main_menu.menu, 7, 1);
/* Set menu mark to the string " * " */
set_menu_mark(my_menu, " * ");
set_menu_mark(main_menu.menu, " * ");
/* Print a border around the main window and print a title */
box(my_menu_win, 0, 0);
box(main_menu.win, 0, 0);
print_in_middle(my_menu_win, 1, 0, 40, "Main Menu", COLOR_PAIR(1));
mvwaddch(my_menu_win, 2, 0, ACS_LTEE);
mvwhline(my_menu_win, 2, 1, ACS_HLINE, 38);
mvwaddch(my_menu_win, 2, 39, ACS_RTEE);
wprint_in_middle(main_menu.win, 1, 0, 40, L"Main Menu", COLOR_PAIR(1));
mvwaddch(main_menu.win, 2, 0, ACS_LTEE);
mvwhline(main_menu.win, 2, 1, ACS_HLINE, 38);
mvwaddch(main_menu.win, 2, 39, ACS_RTEE);
/* Post the menu */
post_menu(my_menu);
wrefresh(my_menu_win);
post_menu(main_menu.menu);
wrefresh(main_menu.win);
attron(COLOR_PAIR(2));
mvprintw(LINES - 2, 0,
@ -70,35 +77,39 @@ void main_menu() {
attroff(COLOR_PAIR(2));
refresh();
int c;
while ((c = getch()) != KEY_F(1)) {
switch (c) {
case KEY_DOWN:
menu_driver(my_menu, REQ_DOWN_ITEM);
break;
case KEY_UP:
menu_driver(my_menu, REQ_UP_ITEM);
break;
case KEY_NPAGE:
menu_driver(my_menu, REQ_DOWN_ITEM);
case 'j':
menu_driver(main_menu.menu, REQ_DOWN_ITEM);
break;
case KEY_UP:
case KEY_PPAGE:
menu_driver(my_menu, REQ_UP_ITEM);
case 'k':
menu_driver(main_menu.menu, REQ_UP_ITEM);
break;
case 10: // ENTER
move(20, 0);
clrtoeol();
mvprintw(20, 0, "Item selected is : %s",
item_name(current_item(my_menu)));
pos_menu_cursor(my_menu);
clear();
if (item_index(current_item(main_menu.menu)) == ARRAY_SIZE(choices) - 1) {
goto close_menu;
}
choicesFuncs[item_index(current_item(main_menu.menu))]();
current_allocated = &main_menu_allocated;
pos_menu_cursor(main_menu.menu);
wrefresh(main_menu.win);
refresh();
redrawwin(main_menu.win);
break;
}
wrefresh(my_menu_win);
wrefresh(main_menu.win);
}
close_menu:
/* Unpost and free all the memory taken up */
unpost_menu(my_menu);
free_menu(my_menu);
for (i = 0; i < n_choices; ++i)
free_item(my_items[i]);
unpost_menu(main_menu.menu);
delete_all(&main_menu_allocated);
endwin();
}

View File

@ -1 +1,7 @@
void main_menu();
// header guard
#ifndef _ba_mm_hg_
#define _ba_mm_hg_
void main_menu();
#endif

View File

@ -1,63 +1,60 @@
#include "marks.h"
#include "helper_funcs.h"
#include "memory.h"
#include "net.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <curses.h>
#include <fstream>
#include <format>
#include <menu.h>
#include <nlohmann/json.hpp>
#include <panel.h>
#include <string>
#include <vector>
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
/*
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.
*/
// MIT License (see original file)
#define NLINES 10
#define NCOLS 40
void init_wins(WINDOW **wins, int n, json marks_json);
void win_show(WINDOW *win, char *label, int label_color);
#define DEFAULT_X_OFFSET 10
#define DEFAULT_Y_OFFSET 2
#define DEFAULT_PADDING 4
std::vector<allocation> marks_allocated;
void init_wins(WINDOW **wins, const int n, const json &marks_json);
void win_show(WINDOW *win, const wchar_t *label, const int label_color,
int width, int height, const json &marks_json,
const int SubjectIndex);
void marks_page() {
current_allocated = &marks_allocated;
curs_set(0);
// DONT FORGET TO UNCOMMENT
// json resp_from_api = bakaapi::get_grades();
std::ifstream f("test-data/marks2.json");
json resp_from_api = json::parse(f);
// thanks to lambda i can make this const
const json resp_from_api = [&]() -> json {
const std::string endpoint = "api/3/marks";
return bakaapi::get_data_from_endpoint(endpoint, GET);
}();
WINDOW **my_wins;
size_t size_my_wins = resp_from_api["Subjects"].size();
my_wins = new (std::nothrow) WINDOW *[size_my_wins];
const size_t size_my_wins = resp_from_api["Subjects"].size();
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();
my_panels = new (std::nothrow) PANEL *[size_my_panels];
const size_t size_my_panels = size_my_wins;
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;
int ch;
@ -70,78 +67,157 @@ void marks_page() {
keypad(stdscr, TRUE);
/* Initialize all the colors */
for (size_t i = 0; i < 8; i++) {
for (uint8_t i = 0; i < 8; i++) {
init_pair(i, i, COLOR_BLACK);
}
init_wins(my_wins, resp_from_api["Subjects"].size(), resp_from_api);
for (size_t i = 0; i < size_my_panels; i++) {
/* Attach a panel to each window Order is bottom up */
my_panels[i] = new_panel(my_wins[i]);
/* Set up the user pointers to the next panel */
if ((i + 1) < size_my_panels) {
set_panel_userptr(my_panels[i], my_panels[(i + 1)]);
} else {
set_panel_userptr(my_panels[i], my_panels[0]);
}
// store all original window position
int *original_y = new int[size_my_wins];
marks_allocated.push_back({GENERIC_ARRAY, original_y, size_my_wins});
int *original_x = new int[size_my_wins];
marks_allocated.push_back({GENERIC_ARRAY, original_x, size_my_wins});
for (size_t i = 0; i < size_my_wins; ++i) {
getbegyx(my_wins[i], original_y[i], original_x[i]);
}
// Update the stacking order.
update_panels();
// Attach 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));
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));
doupdate();
top = my_panels[2];
top = my_panels[size_my_panels - 1];
long y_offset = 0;
// Main loop
while ((ch = getch()) != KEY_F(1)) {
bool needs_update = false;
switch (ch) {
case 9:
top = (PANEL *)panel_userptr(top);
top_panel(top);
case KEY_UP:
case 'k': // Vim-style up
y_offset--;
needs_update = true;
break;
case KEY_DOWN:
case 'j': // Vim-style down
y_offset++;
needs_update = true;
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();
clear();
delete_all(&marks_allocated);
curs_set(1);
}
/* Put all the windows */
void init_wins(WINDOW **wins, int n, json marks_json) {
void init_wins(WINDOW **wins, const int n, const json &marks_json) {
int x, y, i;
char label[1500];
wchar_t label[1500];
y = 2;
x = 10;
y = DEFAULT_Y_OFFSET;
x = DEFAULT_X_OFFSET;
uint8_t curent_color = 0;
for (i = 0; i < n; ++i) {
wins[i] = newwin(NLINES, NCOLS, y, x);
{
std::string sub_name = marks_json["Subjects"][i]["Subject"]["Name"];
std::string sub_avg_s = marks_json["Subjects"][i]["AverageText"];
sprintf(label, "%s - avg: %s", sub_name.c_str(), sub_avg_s.c_str());
unsigned int MaxHight = 0;
// this loop through subjects
for (i = 0; i < n; ++i) {
// Calculate label and max_text_length to determine window width
const std::string sub_name = marks_json["Subjects"][i]["Subject"]["Name"];
const std::string sub_avg_s = marks_json["Subjects"][i]["AverageText"];
// Convert to wchar_t
const std::wstring wsub_name = string_to_wstring(sub_name);
const 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 (unsigned int j = 0; j < static_cast<unsigned int>(
marks_json["Subjects"][i]["Marks"].size());
j++) {
const std::string caption =
rm_tr_le_whitespace(marks_json["Subjects"][i]["Marks"][j]["Caption"]);
const std::string theme =
rm_tr_le_whitespace(marks_json["Subjects"][i]["Marks"][j]["Theme"]);
const std::wstring wcaption = string_to_wstring(caption);
const std::wstring wtheme = string_to_wstring(theme);
// Some code that does something and fixes some edge cases
const std::string testCaption =
caption + std::format(" {{{}}} [{}]", "X", 0);
const std::wstring wTestCaption = string_to_wstring(testCaption);
max_text_length =
std::max({max_text_length, wTestCaption.length(), wtheme.length()});
}
curent_color++;
if (curent_color >= 7) {
curent_color = 1;
int width = max_text_length + DEFAULT_PADDING;
// handle windows overflowing off screen
if (x + width > COLS) {
x = DEFAULT_X_OFFSET;
y += MaxHight + 2;
MaxHight = 0;
}
win_show(wins[i], label, curent_color);
x += 40;
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);
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;
x += width + 5;
}
}
/* Show the window with a border and a label */
void win_show(WINDOW *win, char *label, int label_color) {
int startx, starty, height, width;
height = 20;
void win_show(WINDOW *win, const wchar_t *label, const int label_color,
int width, int height, const json &marks_json,
const int SubjectIndex) {
wresize(win, height, strlen(label) + 4);
// is the compiler smoking weed or something, why is it thinking starty is not
// used ??
int startx, starty;
wresize(win, height, width);
getbegyx(win, starty, startx);
getmaxyx(win, height, width);
@ -151,5 +227,49 @@ void win_show(WINDOW *win, char *label, int label_color) {
mvwhline(win, 2, 1, ACS_HLINE, width - 2);
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));
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++;
}
}

View File

@ -1 +1,5 @@
void marks_page();
// header guard
#ifndef _ba_mp_hg_
#define _ba_mp_hg_
void marks_page();
#endif

92
src/memory.cpp Normal file
View File

@ -0,0 +1,92 @@
#include "memory.h"
#include "color.h"
#include "types.h"
#include <curses.h>
#include <iostream>
#include <menu.h>
#include <ncurses.h>
#include <panel.h>
template <typename T> struct NcursesDeleter {
static void delete_element(T obj);
};
template <> void NcursesDeleter<WINDOW *>::delete_element(WINDOW *win) {
delwin(win);
}
template <> void NcursesDeleter<PANEL *>::delete_element(PANEL *pan) {
del_panel(pan);
}
template <> void NcursesDeleter<ITEM *>::delete_element(ITEM *item) {
free_item(item);
}
template <typename T> void delete_ncurses_arrays(void *ptr, std::size_t size) {
T *array = static_cast<T *>(ptr);
for (std::size_t j = 0; j < size; ++j) {
NcursesDeleter<T>::delete_element(array[j]);
}
delete[] array;
}
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: {
delete_ncurses_arrays<WINDOW *>(allocated->at(i).ptr,
allocated->at(i).size);
break;
}
case PANEL_ARRAY: {
delete_ncurses_arrays<PANEL *>(allocated->at(i).ptr,
allocated->at(i).size);
break;
}
case ITEM_ARRAY: {
delete_ncurses_arrays<ITEM *>(allocated->at(i).ptr,
allocated->at(i).size);
break;
}
case CHAR_PTR_ARRAY: {
char **array = static_cast<char **>(allocated->at(i).ptr);
for (std::size_t j = 0; j < allocated->at(i).size; ++j) {
delete[] array[j];
}
delete[] array;
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 MENU_TYPE:
free_menu(static_cast<MENU *>(allocated->at(i).ptr));
break;
case COMPLETE_MENU_TYPE: {
free_menu(static_cast<complete_menu *>(allocated->at(i).ptr)->menu);
delwin(static_cast<complete_menu *>(allocated->at(i).ptr)->win);
delete_ncurses_arrays<ITEM *>(
static_cast<complete_menu *>(allocated->at(i).ptr)->items,
static_cast<complete_menu *>(allocated->at(i).ptr)->items_size);
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();
}
}

28
src/memory.h Normal file
View File

@ -0,0 +1,28 @@
// header guard
#ifndef _ba_me_hg_
#define _ba_me_hg_
#include <cstddef>
#include <menu.h>
#include <vector>
enum AllocationType {
WINDOW_ARRAY,
PANEL_ARRAY,
ITEM_ARRAY,
CHAR_PTR_ARRAY,
GENERIC_ARRAY,
WINDOW_TYPE,
PANEL_TYPE,
MENU_TYPE,
COMPLETE_MENU_TYPE,
GENERIC_TYPE
};
struct allocation {
AllocationType type;
void *ptr;
std::size_t size;
};
void delete_all(std::vector<allocation> *allocated);
#endif

View File

@ -1,83 +1,150 @@
#include "net.h"
#include "color.h"
#include "const.h"
#include "helper_funcs.h"
#include "main.h"
#include <cerrno>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <curl/curl.h>
#include <curl/header.h>
#include <curses.h>
#include <dirent.h>
#include <fcntl.h>
#include <format>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <nlohmann/json_fwd.hpp>
#include <stdlib.h>
#include <string>
#include <unistd.h>
#define ROUND_UP(x, y) ((((x) + (y) - 1)) & ~((y) - 1))
using nlohmann::json;
std::string access_token;
CURL *curl = curl_easy_init();
// Callback function to write data into a std::string
size_t WriteCallback(void *contents, size_t size, size_t nmemb,
std::string *userp) {
size_t WriteCallback_to_string(void *contents, size_t size, size_t nmemb,
void *userp) {
size_t totalSize = size * nmemb;
userp->append((char *)contents, totalSize);
static_cast<std::string *>(userp)->append((char *)contents, totalSize);
return totalSize;
}
std::tuple<std::string, int> send_curl_request(std::string endpoint,
std::string type,
std::string req_data) {
size_t WriteCallback_to_file(void *contents, size_t size, size_t nmemb,
void *userp) {
const size_t sector_size = 4096;
size_t total = size * nmemb;
void *aligned_buf = aligned_alloc(sector_size, ROUND_UP(total, sector_size));
// Zero out the buffer before copying
memset(aligned_buf, 0, ROUND_UP(total, sector_size));
memcpy(aligned_buf, contents, total);
// Cast userp to fstream*
std::fstream *fileStream = static_cast<std::fstream *>(userp);
// Write to the file using fstream
fileStream->write(static_cast<char *>(aligned_buf), total);
// Check if write was successful
if (fileStream->fail()) {
free(aligned_buf);
return 0;
}
free(aligned_buf);
return total;
}
std::tuple<std::string, int> send_curl_request(
std::string endpoint, metod type, std::string req_data,
size_t (*WriteCallback_function)(void *, size_t, size_t,
void *) = WriteCallback_to_string,
std::fstream *fileStream = nullptr) {
std::string response;
std::string url = baka_api_url + endpoint;
if (type == GET) {
url.append("?" + req_data);
}
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback_function);
if (fileStream) {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fileStream);
} else {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
}
if (config.ignoressl) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req_data.c_str());
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip, deflate");
struct curl_slist *headers = NULL;
headers = curl_slist_append(
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);
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);
break;
default:
std::cerr << RED "[ERROR] " << RESET "invalid metod\n";
safe_exit(EINVAL);
}
CURLcode curl_return_code = curl_easy_perform(curl);
curl_slist_free_all(headers);
if (curl_return_code != CURLE_OK) {
std::cerr << RED "[ERROR] " << RESET << "curl_easy_perform() failed: "
<< curl_easy_strerror(curl_return_code) << "\n";
safe_exit(21);
}
if (fileStream) {
fileStream->close();
}
int http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
return {response, http_code};
} else {
std::cerr << RED "[ERROR] " << RESET "curl not initialised\n";
safe_exit(20);
// prevent compiler warning
return {"", -1};
}
curl_easy_perform(curl); // Perform the request
int http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl); // Cleanup
return {response, http_code};
}
namespace bakaapi {
std::string access_token;
void login(std::string username, std::string password) {
std::string req_data =
std::format("client_id=ANDR&grant_type=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) {
std::cerr << RED "[ERROR] " << RESET << "login failed " << http_code
<< " is non 200 response\n";
@ -89,7 +156,7 @@ void login(std::string username, std::string password) {
{
std::string savedir_path = std::getenv("HOME");
savedir_path.append("/.local/share/bakatui");
std::string urlfile_path = std::string(savedir_path) + "/url";
std::ofstream urlfile;
urlfile.open(urlfile_path);
@ -100,12 +167,12 @@ void login(std::string username, std::string password) {
json resp_parsed = json::parse(response);
access_token = resp_parsed["access_token"];
// DEBUG
std::cout << "access token: " << access_token << std::endl;
}
void refresh_access_token() {
if (config.verbose) {
std::clog << "refreshing access token please wait...\n";
}
json authfile_parsed = json::parse(SoRAuthFile(false, ""));
@ -116,7 +183,7 @@ void refresh_access_token() {
"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) {
std::cerr << RED "[ERROR] " << RESET << http_code
<< "is non 200 response\n";
@ -130,21 +197,74 @@ void refresh_access_token() {
access_token = resp_parsed["access_token"];
}
json get_grades() {
if(access_token.empty()) {
void is_access_token_empty() {
if (access_token.empty()) {
json authfile_parsed = json::parse(SoRAuthFile(false, ""));
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);
if(http_code != 200) {
refresh_access_token();
}
return json::parse(response);
}
// supports all endpoints that only require access_token
json get_data_from_endpoint(const std::string &endpoint, metod metod,
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));
}
auto [response, http_code] = send_curl_request(endpoint, metod, req_data);
if (http_code != 200) {
refresh_access_token();
goto access_token_refreshed;
}
return json::parse(response);
}
int download_attachment(std::string id, std::string path) {
if (config.verbose) {
std::clog << "downloading attachment please wait...\n";
}
if (path[0] == '~') {
path = std::string(std::getenv("HOME")) + path.substr(1);
}
std::string savedir_path = std::filesystem::path(path).parent_path().string();
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::fstream fileStream(path, std::ios::out | std::ios::binary);
if (!fileStream.is_open()) {
std::cerr << RED "[ERROR] " RESET << "Failed to open file for writing\n";
safe_exit(EXIT_FAILURE);
}
is_access_token_empty();
access_token_refreshed:
std::string req_data =
std::format("Authorization=Bearer&access_token={}", access_token);
auto [response, http_code] =
send_curl_request(std::format("/api/3/komens/attachment/{}", id), GET,
req_data, WriteCallback_to_file, &fileStream);
if (http_code != 200) {
if (config.verbose) {
std::clog << BLUE "[LOG] " RESET << "download failed " << http_code
<< " is non 200 response\n";
}
refresh_access_token();
goto access_token_refreshed;
}
return 0;
}
} // namespace bakaapi

View File

@ -1,6 +1,15 @@
#include <curl/curl.h>
#include <nlohmann/json.hpp>
#include <string>
// header guard
#ifndef _ba_ne_hg_
#define _ba_ne_hg_
// metods
enum metod {
GET,
POST,
};
using nlohmann::json;
@ -8,5 +17,8 @@ extern CURL *curl;
namespace bakaapi {
void login(std::string username, std::string password);
void refresh_access_token();
json get_grades();
} // namespace bakaapi
json get_data_from_endpoint(const std::string &endpoint, metod metod,
std::string additional_data = "");
int download_attachment(std::string id, std::string path);
} // namespace bakaapi
#endif

726
src/timetable.cpp Normal file
View File

@ -0,0 +1,726 @@
#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 <climits>
#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
#define REMOVED_COLOR_PAIR COLOR_GREEN
#define ROOMCHANGED_COLOR_PAIR COLOR_MAGENTA
#define SUBSTITUTION_COLOR_PAIR COLOR_YELLOW
#define ADDED_COLOR_PAIR COLOR_BLUE
#define HELP_TEXT \
"Arrows/hjkl to select | ENTER to show info | p/n to select weeks |F1 to " \
"exit"
#define HELP_TEXT_LENGTH sizeof(HELP_TEXT) - 1
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,
const json &resp_from_api);
void draw_lessons(WINDOW **&lesson_windows, uint8_t num_of_columns,
uint16_t cell_width,
const std::vector<uint8_t> &HourIdLookupTable,
const 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,
const std::vector<uint8_t> &HourIdLookupTable,
const 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;
}
const json *partial_json_by_id(const 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(const json &resp_from_api, const 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>());
}
const json *
find_atom_by_indexes(const 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:
clear();
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";
const json resp_from_api =
bakaapi::get_data_from_endpoint(endpoint, GET, 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, HELP_TEXT);
attroff(COLOR_PAIR(COLOR_BLUE));
{
constexpr char *change_types[] = {"Canceled/Removed", "RoomChanged",
"Substitution", "Added"};
constexpr uint8_t change_types_colors[] = {
REMOVED_COLOR_PAIR, ROOMCHANGED_COLOR_PAIR, SUBSTITUTION_COLOR_PAIR,
ADDED_COLOR_PAIR};
for (uint8_t i = 0; i < ARRAY_SIZE(change_types); i++) {
init_pair(UCHAR_MAX - i, COLOR_BLACK, change_types_colors[i]);
}
uint8_t text_offset = 1;
for (uint8_t i = 0; i < ARRAY_SIZE(change_types); i++) {
attron(COLOR_PAIR(UCHAR_MAX - i));
mvprintw(LINES - 2, HELP_TEXT_LENGTH + text_offset, "%s",
change_types[i]);
attroff(COLOR_PAIR(UCHAR_MAX - i));
text_offset += strlen(change_types[i]) + 1;
}
}
attron(COLOR_PAIR(COLOR_BLUE));
{
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)
uint8_t days_back = (current_wday >= start_day)
? (current_wday - start_day)
: (current_wday + 7 - start_day);
// Calculate days forward to end day (handles week wraparound)
uint8_t 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
const 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,
const 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,
const std::vector<uint8_t> &HourIdLookupTable,
const 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,
const std::vector<uint8_t> &HourIdLookupTable,
const json &resp_from_api) {
for (uint8_t i = 0; i < num_of_days; i++) {
for (uint8_t j = 0; j < num_of_columns; j++) {
const 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(REMOVED_COLOR_PAIR));
box(cells[i][j], 0, 0);
wattroff(cells[i][j], COLOR_PAIR(REMOVED_COLOR_PAIR));
break;
case "RoomChanged"_sh:
wattron(cells[i][j], COLOR_PAIR(ROOMCHANGED_COLOR_PAIR));
box(cells[i][j], 0, 0);
wattroff(cells[i][j], COLOR_PAIR(ROOMCHANGED_COLOR_PAIR));
break;
case "Substitution"_sh:
wattron(cells[i][j], COLOR_PAIR(SUBSTITUTION_COLOR_PAIR));
box(cells[i][j], 0, 0);
wattroff(cells[i][j], COLOR_PAIR(SUBSTITUTION_COLOR_PAIR));
break;
case "Added"_sh:
wattron(cells[i][j], COLOR_PAIR(ADDED_COLOR_PAIR));
box(cells[i][j], 0, 0);
wattroff(cells[i][j], COLOR_PAIR(ADDED_COLOR_PAIR));
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
src/timetable.h Normal file
View File

@ -0,0 +1,6 @@
// header guard
#ifndef _ba_ti_hg_
#define _ba_ti_hg_
void timetable_page();
#endif

155
src/types.h Normal file
View File

@ -0,0 +1,155 @@
// header guard
#ifndef _ba_ty_hg_
#define _ba_ty_hg_
#include <istream>
#include <menu.h>
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) {}
};
struct complete_menu {
WINDOW *win;
ITEM **items;
size_t items_size;
MENU *menu;
};
#endif