I'll implement a C++ application that recreates the study tracker with SDL visualizations for bar charts and pie charts. This will require creating a graphical interface for the charts while maintaining the core functionality from the provided code.
main.cpp
This C++ implementation includes:
To compile this code, you'll need to install the SDL2 and SDL2_ttf libraries. On most Linux systems, you can install them with:
sudo apt-get install libsdl2-dev libsdl2-ttf-devThen compile with:
g++ -o study_tracker main.cpp -lSDL2 -lSDL2_ttf -lmThe program saves visualization data to text files and can also export the charts as BMP images. The text files follow a simple CSV format that can be easily imported into other visualization tools if needed.
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <ctime>
#include <iomanip>
#include <map>
#include <limits>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>These lines include necessary C++ standard libraries and SDL libraries:
iostream: For input/output operationsstring: For string manipulationvector: For dynamic arraysfstream: For file operationssstream: For string stream operationsalgorithm: For algorithms like sortingctime: For time-related functionsiomanip: For input/output formattingmap: For key-value pair collectionslimits: For numeric limitsSDL2/SDL.h: For SDL graphics functionalitySDL2/SDL_ttf.h: For SDL text renderingnamespace Utils {
std::string formatDate(time_t timestamp) {
struct tm* timeInfo = localtime(×tamp);
char buffer[20];
strftime(buffer, sizeof(buffer), "%Y-%m-%d", timeInfo);
return std::string(buffer);
}Utils namespace to organize utility functionsformatDate converts a Unix timestamp to a formatted date string (YYYY-MM-DD)localtime to convert timestamp to local timestrftime to format the time into a string std::string formatDateTime(time_t timestamp) {
struct tm* timeInfo = localtime(×tamp);
char buffer[25];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeInfo);
return std::string(buffer);
}formatDate but includes time (YYYY-MM-DD HH:MM:SS) std::string formatDuration(int seconds) {
if (seconds < 60) return std::to_string(seconds) + "s";
else if (seconds < 3600) return std::to_string(seconds / 60) + "m";
else return std::to_string(seconds / 3600) + "h " + std::to_string((seconds % 3600) / 60) + "m";
} time_t parseDateTime(const std::string& dateTimeStr) {
struct tm timeInfo = {0};
if (dateTimeStr.length() >= 10) {
if (sscanf(dateTimeStr.c_str(), "%d-%d-%d",
&timeInfo.tm_year, &timeInfo.tm_mon, &timeInfo.tm_mday) == 3) {
timeInfo.tm_year -= 1900;
timeInfo.tm_mon -= 1;
return mktime(&timeInfo);
}
}
return 0;
}sscanf to extract year, month, and daytm struct SDL_Color getColor(int index) {
SDL_Color colors[] = {
{255, 99, 132, 255}, // Red
{54, 162, 235, 255}, // Blue
{255, 206, 86, 255}, // Yellow
{75, 192, 192, 255}, // Green
{153, 102, 255, 255}, // Purple
{255, 159, 64, 255}, // Orange
{199, 199, 199, 255}, // Gray
{83, 123, 196, 255}, // Blue-ish
{234, 153, 153, 255}, // Pink-ish
{139, 195, 74, 255} // Light green
};
return colors[index % 10];
}
}class StudySession {
private:
int id;
std::string subject;
time_t startTime;
time_t endTime;
int duration;
std::string notes;Private member variables:
id: Unique identifier for the sessionsubject: The subject being studiedstartTime: When the session started (Unix timestamp)endTime: When the session ended (Unix timestamp)duration: Length of session in secondsnotes: Optional notes about the sessionpublic:
StudySession(int id, const std::string& subject, time_t startTime, time_t endTime, const std::string& notes = "")
: id(id), subject(subject), startTime(startTime), endTime(endTime), notes(notes) {
duration = difftime(endTime, startTime);
}duration using difftime to get seconds between start and end int getId() const { return id; }
std::string getSubject() const { return subject; }
time_t getStartTime() const { return startTime; }
time_t getEndTime() const { return endTime; }
int getDuration() const { return duration; }
std::string getNotes() const { return notes; }const as they don't modify the object std::string toString() const {
std::stringstream ss;
ss << "Session #" << id << ": " << subject << " | "
<< "Start: " << Utils::formatDateTime(startTime) << " | "
<< "End: " << Utils::formatDateTime(endTime) << " | "
<< "Duration: " << Utils::formatDuration(duration);
if (!notes.empty()) ss << " | Notes: " << notes;
return ss.str();
}stringstream for efficient string building std::string serialize() const {
std::stringstream ss;
ss << id << "|" << subject << "|" << startTime << "|" << endTime << "|" << notes;
return ss.str();
}|) as a delimiter between fields static StudySession deserialize(const std::string& data) {
std::stringstream ss(data);
std::string item;
std::vector<std::string> parts;
while (std::getline(ss, item, '|')) parts.push_back(item);
if (parts.size() >= 4) {
return StudySession(std::stoi(parts[0]), parts[1], std::stoll(parts[2]),
std::stoll(parts[3]), parts.size() > 4 ? parts[4] : "");
}
return StudySession(0, "Unknown", 0, 0);
}
};class TodoItem {
private:
int id;
std::string description;
bool completed;
int priority; // 1-High, 2-Medium, 3-Low
time_t dueDate;
std::string subject;Private member variables:
id: Unique identifierdescription: Task descriptioncompleted: Completion statuspriority: Importance level (1=High, 2=Medium, 3=Low)dueDate: When the task is due (Unix timestamp)subject: Related subjectpublic:
TodoItem(int id, const std::string& desc, int priority = 2,
time_t dueDate = 0, const std::string& subject = "", bool completed = false)
: id(id), description(desc), completed(completed),
priority(priority), dueDate(dueDate), subject(subject) {} int getId() const { return id; }
std::string getDescription() const { return description; }
bool isCompleted() const { return completed; }
int getPriority() const { return priority; }
time_t getDueDate() const { return dueDate; }
std::string getSubject() const { return subject; }
void setCompleted(bool status) { completed = status; }setCompleted allows changing the completion status std::string getPriorityString() const {
switch (priority) {
case 1: return "High";
case 2: return "Medium";
case 3: return "Low";
default: return "Unknown";
}
} std::string toString() const {
std::stringstream ss;
ss << "[" << (completed ? "X" : " ") << "] " << description;
if (!subject.empty()) ss << " (" << subject << ")";
ss << " - Priority: " << getPriorityString();
if (dueDate > 0) ss << ", Due: " << Utils::formatDate(dueDate);
return ss.str();
} std::string serialize() const {
std::stringstream ss;
ss << id << "|" << description << "|" << (completed ? "1" : "0") << "|"
<< priority << "|" << dueDate << "|" << subject;
return ss.str();
}|) as a delimiter between fieldscompleted as "1" or "0" static TodoItem deserialize(const std::string& data) {
std::stringstream ss(data);
std::string item;
std::vector<std::string> parts;
while (std::getline(ss, item, '|')) parts.push_back(item);
if (parts.size() >= 5) {
return TodoItem(std::stoi(parts[0]), parts[1], std::stoi(parts[3]),
std::stoll(parts[4]), parts.size() > 5 ? parts[5] : "", parts[2] == "1");
}
return TodoItem(0, "Unknown task");
}
};class TodoList {
private:
std::vector<TodoItem> items;
int nextId;Private member variables:
items: Vector to store all to-do itemsnextId: Tracks the next available ID for new itemspublic:
TodoList() : nextId(1) {}nextId to 1 void addItem(const std::string& description, int priority = 2,
time_t dueDate = 0, const std::string& subject = "") {
items.push_back(TodoItem(nextId++, description, priority, dueDate, subject));
}nextId and then increments it bool removeItem(int id) {
auto it = std::find_if(items.begin(), items.end(),
[id](const TodoItem& item) { return item.getId() == id; });
if (it != items.end()) {
items.erase(it);
return true;
}
return false;
}std::find_if with a lambda to find the item bool markAsCompleted(int id, bool status = true) {
for (auto& item : items) {
if (item.getId() == id) {
item.setCompleted(status);
return true;
}
}
return false;
} const std::vector<TodoItem>& getAllItems() const { return items; } std::vector<TodoItem> getIncompleteItems() const {
std::vector<TodoItem> result;
for (const auto& item : items)
if (!item.isCompleted()) result.push_back(item);
return result;
} std::vector<TodoItem> getItemsSortedByPriority() const {
std::vector<TodoItem> result = items;
std::sort(result.begin(), result.end(),
[](const TodoItem& a, const TodoItem& b) {
return a.getPriority() < b.getPriority();
});
return result;
}std::sort with a lambda comparator std::vector<TodoItem> getItemsSortedByDueDate() const {
std::vector<TodoItem> result = items;
std::sort(result.begin(), result.end(),
[](const TodoItem& a, const TodoItem& b) {
if (a.getDueDate() == 0) return false;
if (b.getDueDate() == 0) return true;
return a.getDueDate() < b.getDueDate();
});
return result;
} bool saveToFile(const std::string& filename) const {
std::ofstream file(filename);
if (!file.is_open()) return false;
for (const auto& item : items) file << item.serialize() << std::endl;
return true;
} bool loadFromFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) return false;
items.clear();
nextId = 1;
std::string line;
while (std::getline(file, line)) {
if (!line.empty()) {
TodoItem item = TodoItem::deserialize(line);
items.push_back(item);
if (item.getId() >= nextId) nextId = item.getId() + 1;
}
}
return true;
}
};class User {
private:
int id;
std::string username;
std::string password;
std::string fullName;
std::vector<StudySession> sessions;
TodoList todoList;
int nextSessionId;Private member variables:
id: Unique identifierusername: Login usernamepassword: Login password (stored in plain text - not secure)fullName: User's full namesessions: Vector of study sessionstodoList: User's to-do listnextSessionId: Tracks next available session IDpublic:
User(int id, const std::string& username, const std::string& password, const std::string& fullName = "")
: id(id), username(username), password(password), fullName(fullName), nextSessionId(1) {}nextSessionId to 1 int getId() const { return id; }
std::string getUsername() const { return username; }
std::string getFullName() const { return fullName; }
bool verifyPassword(const std::string& pwd) const { return password == pwd; }verifyPassword checks if provided password matches stored password int startSession(const std::string& subject, time_t startTime, const std::string& notes = "") {
sessions.push_back(StudySession(nextSessionId, subject, startTime, time(nullptr), notes));
return nextSessionId++;
}nextSessionId bool endSession(int sessionId, time_t endTime) {
for (auto& session : sessions) {
if (session.getId() == sessionId) {
session = StudySession(session.getId(), session.getSubject(),
session.getStartTime(), endTime, session.getNotes());
return true;
}
}
return false;
} StudySession* getSession(int sessionId) {
for (auto& session : sessions)
if (session.getId() == sessionId) return &session;
return nullptr;
} std::vector<StudySession> getAllSessions() const { return sessions; } std::map<std::string, int> getTimePerSubject() const {
std::map<std::string, int> result;
for (const auto& session : sessions) {
std::string subject = session.getSubject();
if (result.find(subject) == result.end()) result[subject] = session.getDuration();
else result[subject] += session.getDuration();
}
return result;
} int getTotalStudyTime() const {
int total = 0;
for (const auto& session : sessions) total += session.getDuration();
return total;
} TodoList& getTodoList() { return todoList; }
const TodoList& getTodoList() const { return todoList; } bool saveUserData(const std::string& sessionFile, const std::string& todoFile) const {
std::ofstream sessionOut(sessionFile);
if (!sessionOut.is_open()) return false;
for (const auto& session : sessions) sessionOut << session.serialize() << std::endl;
sessionOut << "NEXT_ID=" << nextSessionId << std::endl;
sessionOut.close();
return todoList.saveToFile(todoFile);
}nextSessionId in the sessions file bool loadUserData(const std::string& sessionFile, const std::string& todoFile) {
std::ifstream sessionIn(sessionFile);
if (sessionIn.is_open()) {
sessions.clear();
nextSessionId = 1;
std::string line;
while (std::getline(sessionIn, line)) {
if (line.substr(0, 8) == "NEXT_ID=") nextSessionId = std::stoi(line.substr(8));
else if (!line.empty()) sessions.push_back(StudySession::deserialize(line));
}
sessionIn.close();
}
return todoList.loadFromFile(todoFile);
}
};class SDLVisualization {
protected:
SDL_Window* window;
SDL_Renderer* renderer;
TTF_Font* font;
TTF_Font* titleFont;
User* user;
int width;
int height;
bool initialized;Protected member variables:
window: SDL windowrenderer: SDL rendererfont: Font for regular texttitleFont: Font for titlesuser: Pointer to user datawidth, height: Window dimensionsinitialized: Tracks if SDL is initializedpublic:
SDLVisualization(User* user, int width = 800, int height = 600)
: user(user), width(width), height(height), initialized(false),
window(nullptr), renderer(nullptr), font(nullptr), titleFont(nullptr) {} virtual ~SDLVisualization() {
if (font) TTF_CloseFont(font);
if (titleFont) TTF_CloseFont(titleFont);
if (renderer) SDL_DestroyRenderer(renderer);
if (window) SDL_DestroyWindow(window);
} bool initialize(const std::string& title) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
return false;
}
if (TTF_Init() < 0) {
std::cerr << "SDL_ttf could not initialize! TTF_Error: " << TTF_GetError() << std::endl;
return false;
}
window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
width, height, SDL_WINDOW_SHOWN);
if (!window) {
std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl;
return false;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
std::cerr << "Renderer could not be created! SDL_Error: " << SDL_GetError() << std::endl;
return false;
}
// Load fonts - assuming a font file is available
font = TTF_OpenFont("arial.ttf", 14);
titleFont = TTF_OpenFont("arial.ttf", 24);
if (!font || !titleFont) {
std::cerr << "Failed to load font! TTF_Error: " << TTF_GetError() << std::endl;
// Continue anyway, we'll use primitive rendering
}
initialized = true;
return true;
} void renderText(const std::string& text, int x, int y, SDL_Color color, TTF_Font* useFont = nullptr) {
if (!useFont) useFont = font;
if (useFont) {
SDL_Surface* surface = TTF_RenderText_Blended(useFont, text.c_str(), color);
if (surface) {
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (texture) {
SDL_Rect rect = {x, y, surface->w, surface->h};
SDL_RenderCopy(renderer, texture, NULL, &rect);
SDL_DestroyTexture(texture);
}
SDL_FreeSurface(surface);
}
} else {
// Fallback primitive text rendering (just a placeholder)
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
SDL_Rect rect = {x, y, static_cast<int>(text.length() * 8), 15};
SDL_RenderFillRect(renderer, &rect);
}
} virtual void render() = 0; void saveToFile(const std::string& filename) {
// Create a surface from the renderer
SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, 32, 0, 0, 0, 0);
if (!surface) {
std::cerr << "Could not create surface for screenshot: " << SDL_GetError() << std::endl;
return;
}
// Read pixel data from the renderer
SDL_RenderReadPixels(renderer, NULL, surface->format->format, surface->pixels, surface->pitch);
// Save the surface to a BMP file
if (SDL_SaveBMP(surface, filename.c_str()) != 0) {
std::cerr << "Could not save screenshot: " << SDL_GetError() << std::endl;
} else {
std::cout << "Visualization saved to " << filename << std::endl;
}
SDL_FreeSurface(surface);
} void waitForExit() {
bool quit = false;
SDL_Event e;
while (!quit) {
while (SDL_PollEvent(&e) != 0) {
if (e.type == SDL_QUIT ||
(e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)) {
quit = true;
}
}
SDL_Delay(100);
}
}
};class SDLBarChart : public SDLVisualization {
private:
std::string title;
std::vector<std::pair<std::string, int>> data;
std::string filename;Private member variables:
title: Chart titledata: Vector of label-value pairsfilename: File to save datapublic:
SDLBarChart(User* user, const std::string& title, const std::string& filename = "bar_chart_data.txt")
: SDLVisualization(user, 800, 600), title(title), filename(filename) {} void addDataPoint(const std::string& label, int value) {
data.push_back(std::make_pair(label, value));
} void loadDataFromUser() {
data.clear();
if (user->getAllSessions().empty()) {
// Add sample data for demonstration when no sessions exist
std::cout << "No study sessions found. Showing sample visualization data." << std::endl;
addDataPoint("Sample Math", 3600); // 1 hour
addDataPoint("Sample Science", 1800); // 30 minutes
} else {
// Use real data
std::map<std::string, int> timePerSubject = user->getTimePerSubject();
for (const auto& pair : timePerSubject) {
addDataPoint(pair.first, pair.second);
}
}
// Save data to text file
std::ofstream file(filename);
if (file.is_open()) {
file << "Subject,Duration(seconds)" << std::endl;
for (const auto& pair : data) {
file << pair.first << "," << pair.second << std::endl;
}
file.close();
std::cout << "Bar chart data saved to " << filename << std::endl;
}
} void render() override {
if (!initialized && !initialize("Study Time Bar Chart")) {
return;
}
// Clear screen
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
// Render title
SDL_Color black = {0, 0, 0, 255};
renderText(title, width/2 - 150, 20, black, titleFont);
// Find maximum value for scaling
int maxValue = 0;
for (const auto& pair : data) {
if (pair.second > maxValue) maxValue = pair.second;
}
// If no data or all zeros, set a default max
if (maxValue == 0) maxValue = 1;
// Calculate bar dimensions
int barWidth = (width - 200) / data.size();
int maxBarHeight = height - 150;
int startX = 100;
int startY = height - 50;
// Draw axes
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderDrawLine(renderer, startX, startY, startX, startY - maxBarHeight - 20); // Y-axis
SDL_RenderDrawLine(renderer, startY, startX, startY - maxBarHeight - 20); // Y-axis
SDL_RenderDrawLine(renderer, startX, startY, startX + barWidth * data.size() + 50, startY); // X-axis
// Draw bars
for (size_t i = 0; i < data.size(); i++) {
int barHeight = static_cast<int>((static_cast<double>(data[i].second) / maxValue) * maxBarHeight);
// Get color for this bar
SDL_Color color = Utils::getColor(i);
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
// Draw the bar
SDL_Rect barRect = {startX + static_cast<int>(i) * barWidth + 10, startY - barHeight, barWidth - 20, barHeight};
SDL_RenderFillRect(renderer, &barRect);
// Draw border
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderDrawRect(renderer, &barRect);
// Draw label
std::string label = data[i].first;
if (label.length() > 10) label = label.substr(0, 10) + "...";
renderText(label, startX + static_cast<int>(i) * barWidth + 5, startY + 10, black);
// Draw value
renderText(Utils::formatDuration(data[i].second),
startX + static_cast<int>(i) * barWidth + 5,
startY - barHeight - 20, black);
}
// Draw Y-axis labels
for (int i = 0; i <= 5; i++) {
int y = startY - (i * maxBarHeight / 5);
int value = i * maxValue / 5;
SDL_RenderDrawLine(renderer, startX - 5, y, startX, y); // Tick mark
renderText(Utils::formatDuration(value), startX - 80, y - 10, black);
}
// Present the renderer
SDL_RenderPresent(renderer);
}
};render method for bar chartsclass SDLPieChart : public SDLVisualization {
private:
std::string title;
std::vector<std::pair<std::string, int>> data;
std::string filename;public:
SDLPieChart(User* user, const std::string& title, const std::string& filename = "pie_chart_data.txt")
: SDLVisualization(user, 800, 600), title(title), filename(filename) {} void addDataPoint(const std::string& label, int value) {
data.push_back(std::make_pair(label, value));
} void loadDataFromUser() {
data.clear();
if (user->getAllSessions().empty()) {
// Add sample data for demonstration when no sessions exist
std::cout << "No study sessions found. Showing sample visualization data." << std::endl;
addDataPoint("Sample Math", 3600); // 1 hour
addDataPoint("Sample Science", 1800); // 30 minutes
addDataPoint("Sample English", 2700); // 45 minutes
} else {
// Use real data
std::map<std::string, int> timePerSubject = user->getTimePerSubject();
for (const auto& pair : timePerSubject) {
addDataPoint(pair.first, pair.second);
}
}
// Save data to text file
std::ofstream file(filename);
if (file.is_open()) {
file << "Subject,Duration(seconds)" << std::endl;
for (const auto& pair : data) {
file << pair.first << "," << pair.second << std::endl;
}
file.close();
std::cout << "Pie chart data saved to " << filename << std::endl;
}
} void render() override {
if (!initialized && !initialize("Study Time Distribution Pie Chart")) {
return;
}
// Clear screen
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
// Render title
SDL_Color black = {0, 0, 0, 255};
renderText(title, width/2 - 150, 20, black, titleFont);
// Calculate total value
int total = 0;
for (const auto& pair : data) {
total += pair.second;
}
// If no data, show a message
if (total == 0) {
renderText("No data available", width/2 - 80, height/2, black);
SDL_RenderPresent(renderer);
return;
}
// Calculate pie chart dimensions
int centerX = width / 2;
int centerY = height / 2;
int radius = std::min(width, height) / 3;
// Draw pie slices
double startAngle = 0.0;
for (size_t i = 0; i < data.size(); i++) {
double sliceAngle = 2.0 * M_PI * data[i].second / total;
// Get color for this slice
SDL_Color color = Utils::getColor(i);
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
// Draw the slice (approximated with triangles)
for (double angle = startAngle; angle < startAngle + sliceAngle; angle += 0.01) {
SDL_RenderDrawLine(renderer, centerX, centerY,
centerX + static_cast<int>(radius * cos(angle)),
centerY + static_cast<int>(radius * sin(angle)));
}
// Calculate position for the label (midpoint of the slice)
double labelAngle = startAngle + sliceAngle / 2;
int labelX = centerX + static_cast<int>((radius + 30) * cos(labelAngle));
int labelY = centerY + static_cast<int>((radius + 30) * sin(labelAngle));
// Draw label
std::string label = data[i].first;
if (label.length() > 10) label = label.substr(0, 10) + "...";
renderText(label, labelX - 40, labelY - 10, black);
// Draw percentage
double percentage = 100.0 * data[i].second / total;
std::stringstream ss;
ss << std::fixed << std::setprecision(1) << percentage << "%";
renderText(ss.str(), labelX - 20, labelY + 10, black);
startAngle += sliceAngle;
}
// Draw legend
int legendX = width - 200;
int legendY = 100;
renderText("Legend:", legendX, legendY - 30, black);
for (size_t i = 0; i < data.size(); i++) {
SDL_Color color = Utils::getColor(i);
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
SDL_Rect colorRect = {legendX, legendY + static_cast<int>(i) * 30, 20, 20};
SDL_RenderFillRect(renderer, &colorRect);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderDrawRect(renderer, &colorRect);
std::string label = data[i].first + ": " + Utils::formatDuration(data[i].second);
renderText(label, legendX + 30, legendY + static_cast<int>(i) * 30 + 5, black);
}
// Present the renderer
SDL_RenderPresent(renderer);
}
};render method for pie chartsclass FileManager {
public:
static bool saveUserData(int userId, User* user) {
if (!user) return false;
std::string userDir = "data/user_" + std::to_string(userId);
std::system(("mkdir -p " + userDir).c_str());
return user->saveUserData(userDir + "/sessions.dat", userDir + "/todo.dat");
}saveUserData creates a directory for user data and saves sessions and to-do items static bool loadUserData(int userId, User* user) {
if (!user) return false;
std::string userDir = "data/user_" + std::to_string(userId);
return user->loadUserData(userDir + "/sessions.dat", userDir + "/todo.dat");
}loadUserData loads user data from files static bool saveUserCredentials(const std::vector<User*>& users) {
std::ofstream file("users.dat");
if (!file.is_open()) return false;
for (const auto& user : users) {
if (user) file << user->getId() << "|" << user->getUsername() << "|"
<< user->getFullName() << "|" << std::endl;
}
return true;
}
};saveUserCredentials saves basic user information to a fileUser* currentUser = nullptr;
std::vector<User*> users;
int nextUserId = 1;currentUser points to the logged-in userusers stores all registered usersnextUserId tracks the next available user IDvoid clearScreen() { std::system("clear"); }
void pauseExecution() {
std::cout << "\nPress Enter to continue...";
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}clearScreen clears the terminalpauseExecution waits for user to press Enterstd::string getStringInput(const std::string& prompt) {
std::cout << prompt;
std::string input;
std::getline(std::cin, input);
return input;
}
std::string getPasswordInput() {
std::cout << "Enter password: ";
std::string password;
std::getline(std::cin, password);
return password;
}getPasswordInput is a simple password input (no masking)int getIntInput(const std::string& prompt) {
std::cout << prompt;
std::string line;
std::getline(std::cin, line);
try { return line.empty() ? 0 : std::stoi(line); }
catch (...) { return 0; }
}time_t getDateInput(const std::string& prompt) {
std::string dateStr = getStringInput(prompt);
time_t date = Utils::parseDateTime(dateStr);
if (date == 0) {
std::cout << "Invalid date format. Using current time instead." << std::endl;
date = time(nullptr);
}
return date;
}void displayMainMenu();
void displayLoginMenu();
void registerUser();
bool loginUser();
void displayStudyMenu();
void startStudySession();
void endStudySession();
void viewStudySessions();
void displayVisualizationMenu();
void createBarChart();
void createPieChart();
void displayTodoMenu();
void addTodoItem();
void viewTodoList();
void markTodoItemComplete();
void removeTodoItem();
void generateReport();
void viewRankings();void registerUser() {
clearScreen();
std::cout << "===== REGISTRATION =====" << std::endl;
std::string username = getStringInput("Enter username: ");
std::string password = getPasswordInput();
std::string fullName = getStringInput("Enter full name: ");
for (const auto& user : users) {
if (user->getUsername() == username) {
std::cout << "Username already exists." << std::endl;
pauseExecution();
return;
}
}
User* newUser = new User(nextUserId++, username, password, fullName);
users.push_back(newUser);
FileManager::saveUserCredentials(users);
FileManager::saveUserData(newUser->getId(), newUser);
std::cout << "Registration successful." << std::endl;
pauseExecution();
}bool loginUser() {
clearScreen();
std::cout << "===== LOGIN =====" << std::endl;
std::string username = getStringInput("Enter username: ");
std::string password = getPasswordInput();
for (auto& user : users) {
if (user->getUsername() == username) {
if (user->verifyPassword(password)) {
currentUser = user;
FileManager::loadUserData(user->getId(), user);
std::cout << "Login successful. Welcome, " << user->getFullName() << "!" << std::endl;
pauseExecution();
return true;
} else {
std::cout << "Incorrect password." << std::endl;
pauseExecution();
return false;
}
}
}
std::cout << "Username not found." << std::endl;
pauseExecution();
return false;
}void startStudySession() {
clearScreen();
std::cout << "===== START STUDY SESSION =====" << std::endl;
std::string subject = getStringInput("Enter subject: ");
std::string notes = getStringInput("Enter notes (optional): ");
time_t now = time(nullptr);
int sessionId = currentUser->startSession(subject, now, notes);
std::cout << "Study session #" << sessionId << " started at "
<< Utils::formatDateTime(now) << std::endl;
FileManager::saveUserData(currentUser->getId(), currentUser);
pauseExecution();
}void endStudySession() {
clearScreen();
std::cout << "===== END STUDY SESSION =====" << std::endl;
std::vector<StudySession> sessions = currentUser->getAllSessions();
if (sessions.empty()) {
std::cout << "No study sessions found." << std::endl;
pauseExecution();
return;
}
int sessionId = getIntInput("Enter session ID to end (0 for latest): ");
if (sessionId == 0) {
int latestId = 0;
for (const auto& session : sessions)
if (session.getId() > latestId) latestId = session.getId();
sessionId = latestId;
}
time_t now = time(nullptr);
if (currentUser->endSession(sessionId, now)) {
std::cout << "Study session #" << sessionId << " ended at " << Utils::formatDateTime(now) << std::endl;
StudySession* session = currentUser->getSession(sessionId);
if (session) std::cout << "Duration: " << Utils::formatDuration(session->getDuration()) << std::endl;
FileManager::saveUserData(currentUser->getId(), currentUser);
} else {
std::cout << "Session not found or already ended." << std::endl;
}
pauseExecution();
}void viewStudySessions() {
clearScreen();
std::cout << "===== STUDY SESSIONS =====" << std::endl;
std::vector<StudySession> sessions = currentUser->getAllSessions();
if (sessions.empty()) {
std::cout << "No study sessions found." << std::endl;
pauseExecution();
return;
}
std::sort(sessions.begin(), sessions.end(),
[](const StudySession& a, const StudySession& b) {
return a.getStartTime() > b.getStartTime();
});
for (const auto& session : sessions) std::cout << session.toString() << std::endl;
pauseExecution();
}void createBarChart() {
clearScreen();
std::cout << "===== BAR CHART VISUALIZATION =====" << std::endl;
SDLBarChart chart(currentUser, "Study Time Per Subject");
chart.loadDataFromUser();
chart.render();
if (getStringInput("Save this chart as an image? (y/n): ") == "y") {
chart.saveToFile("bar_chart.bmp");
}
std::cout << "Press ESC key to close the chart window." << std::endl;
chart.waitForExit();
}void createPieChart() {
clearScreen();
std::cout << "===== PIE CHART VISUALIZATION =====" << std::endl;
SDLPieChart chart(currentUser, "Subject Distribution");
chart.loadDataFromUser();
chart.render();
if (getStringInput("Save this chart as an image? (y/n): ") == "y") {
chart.saveToFile("pie_chart.bmp");
}
std::cout << "Press ESC key to close the chart window." << std::endl;
chart.waitForExit();
}void addTodoItem() {
clearScreen();
std::cout << "===== ADD TODO ITEM =====" << std::endl;
std::string description = getStringInput("Enter task description: ");
std::string subject = getStringInput("Enter subject (optional): ");
std::cout << "Priority: 1-High, 2-Medium, 3-Low" << std::endl;
int priority = getIntInput("Enter priority (1-3): ");
if (priority < 1 || priority > 3) priority = 2;
time_t dueDate = getDateInput("Enter due date (YYYY-MM-DD) or leave blank: ");
currentUser->getTodoList().addItem(description, priority, dueDate, subject);
std::cout << "Todo item added successfully." << std::endl;
FileManager::saveUserData(currentUser->getId(), currentUser);
pauseExecution();
}void viewTodoList() {
clearScreen();
std::cout << "===== TODO LIST =====" << std::endl;
std::cout << "Sort: 1-Default, 2-Priority, 3-Due Date" << std::endl;
int sortOption = getIntInput("Select sorting option: ");
std::vector<TodoItem> items;
switch (sortOption) {
case 2: items = currentUser->getTodoList().getItemsSortedByPriority(); break;
case 3: items = currentUser->getTodoList().getItemsSortedByDueDate(); break;
default: items = currentUser->getTodoList().getAllItems();
}
if (items.empty()) {
std::cout << "No todo items found." << std::endl;
pauseExecution();
return;
}
std::cout << "ID | Status | Description" << std::endl;
std::cout << "-------------------------------------------" << std::endl;
for (const auto& item : items) {
std::cout << std::setw(3) << item.getId() << " | "
<< (item.isCompleted() ? "[X]" : "[ ]") << " | "
<< item.toString() << std::endl;
}
pauseExecution();
}void markTodoItemComplete() {
clearScreen();
std::cout << "===== MARK ITEM AS COMPLETE =====" << std::endl;
std::vector<TodoItem> items = currentUser->getTodoList().getIncompleteItems();
if (items.empty()) {
std::cout << "No incomplete todo items found." << std::endl;
pauseExecution();
return;
}
std::cout << "Incomplete items:" << std::endl;
for (const auto& item : items) {
std::cout << std::setw(3) << item.getId() << " | " << item.getDescription() << std::endl;
}
int itemId = getIntInput("Enter item ID to mark as complete (0 to cancel): ");
if (itemId == 0) return;
if (currentUser->getTodoList().markAsCompleted(itemId)) {
std::cout << "Item marked as complete." << std::endl;
FileManager::saveUserData(currentUser->getId(), currentUser);
} else {
std::cout << "Item not found or already complete." << std::endl;
}
pauseExecution();
}void removeTodoItem() {
clearScreen();
std::cout << "===== REMOVE TODO ITEM =====" << std::endl;
std::vector<TodoItem> items = currentUser->getTodoList().getAllItems();
if (items.empty()) {
std::cout << "No todo items found." << std::endl;
pauseExecution();
return;
}
std::cout << "All items:" << std::endl;
for (const auto& item : items) {
std::cout << std::setw(3) << item.getId() << " | "
<< (item.isCompleted() ? "[X]" : "[ ]") << " | "
<< item.getDescription() << std::endl;
}
int itemId = getIntInput("Enter item ID to remove (0 to cancel): ");
if (itemId == 0) return;
if (currentUser->getTodoList().removeItem(itemId)) {
std::cout << "Item removed successfully." << std::endl;
FileManager::saveUserData(currentUser->getId(), currentUser);
} else {
std::cout << "Item not found." << std::endl;
}
pauseExecution();
}void generateReport() {
clearScreen();
std::cout << "===== STUDY REPORT =====" << std::endl;
std::vector<StudySession> sessions = currentUser->getAllSessions();
if (sessions.empty()) {
std::cout << "No study data available for report." << std::endl;
pauseExecution();
return;
}
int totalTime = currentUser->getTotalStudyTime();
std::map<std::string, int> timePerSubject = currentUser->getTimePerSubject();
std::string mostStudiedSubject = "";
int maxTime = 0;
for (const auto& pair : timePerSubject) {
if (pair.second > maxTime) {
maxTime = pair.second;
mostStudiedSubject = pair.first;
}
}
std::cout << "Study Summary for " << currentUser->getFullName() << std::endl;
std::cout << "------------------------------------" << std::endl;
std::cout << "Total study sessions: " << sessions.size() << std::endl;
std::cout << "Total study time: " << Utils::formatDuration(totalTime) << std::endl;
std::cout << "Number of subjects: " << timePerSubject.size() << std::endl;
if (!mostStudiedSubject.empty()) {
std::cout << "Most studied: " << mostStudiedSubject << " ("
<< Utils::formatDuration(maxTime) << ")" << std::endl;
}
std::cout << "------------------------------------" << std::endl;
std::cout << "Time per subject:" << std::endl;
for (const auto& pair : timePerSubject) {
std::cout << std::setw(15) << std::left << pair.first << ": "
<< Utils::formatDuration(pair.second) << std::endl;
}
if (getStringInput("Save this report to a file? (y/n): ") == "y") {
std::ofstream file("study_report.txt");
if (file.is_open()) {
file << "Study Summary for " << currentUser->getFullName() << std::endl;
file << "------------------------------------" << std::endl;
file << "Total study sessions: " << sessions.size() << std::endl;
file << "Total study time: " << Utils::formatDuration(totalTime) << std::endl;
file << "Number of subjects: " << timePerSubject.size() << std::endl;
if (!mostStudiedSubject.empty()) {
file << "Most studied: " << mostStudiedSubject << " ("
<< Utils::formatDuration(maxTime) << ")" << std::endl;
}
file << "------------------------------------" << std::endl;
file << "Time per subject:" << std::endl;
for (const auto& pair : timePerSubject) {
file << std::setw(15) << std::left << pair.first << ": "
<< Utils::formatDuration(pair.second) << std::endl;
}
file.close();
std::cout << "Report saved to study_report.txt" << std::endl;
}
}
pauseExecution();
}void viewRankings() {
clearScreen();
std::cout << "===== RANKINGS =====" << std::endl;
int totalTime = currentUser->getTotalStudyTime();
std::string rank = "Beginner";
std::string medal = "None";
if (totalTime > 36000) { rank = "Gold"; medal = "Gold"; }
else if (totalTime > 18000) { rank = "Silver"; medal = "Silver"; }
else if (totalTime > 3600) { rank = "Bronze"; medal = "Bronze"; }
std::cout << "Your Study Rank: " << rank << " (" << medal << ")" << std::endl;
std::cout << "Total Study Time: " << Utils::formatDuration(totalTime) << std::endl;
std::cout << "Ranking Requirements:" << std::endl;
std::cout << "Gold: 10+ hours | Silver: 5+ hours | Bronze: 1+ hour" << std::endl;
pauseExecution();
}void displayMainMenu() {
while (currentUser) {
clearScreen();
std::cout << "===== MAIN MENU =====" << std::endl;
std::cout << "Welcome, " << currentUser->getFullName() << "!" << std::endl;
std::cout << "1. Study Sessions" << std::endl;
std::cout << "2. To-Do List" << std::endl;
std::cout << "3. Visualizations" << std::endl;
std::cout << "4. Reports" << std::endl;
std::cout << "5. Rankings" << std::endl;
std::cout << "6. Logout" << std::endl;
switch (getIntInput("Enter your choice: ")) {
case 1: displayStudyMenu(); break;
case 2: displayTodoMenu(); break;
case 3: displayVisualizationMenu(); break;
case 4: generateReport(); break;
case 5: viewRankings(); break;
case 6:
FileManager::saveUserData(currentUser->getId(), currentUser);
currentUser = nullptr;
return;
default:
std::cout << "Invalid choice." << std::endl;
pauseExecution();
}
}
}void displayLoginMenu() {
while (true) {
clearScreen();
std::cout << "===== STUDY TRACKER =====" << std::endl;
std::cout << "1. Login" << std::endl;
std::cout << "2. Register" << std::endl;
std::cout << "3. Exit" << std::endl;
switch (getIntInput("Enter your choice: ")) {
case 1: if (loginUser()) displayMainMenu(); break;
case 2: registerUser(); break;
case 3: return;
default:
std::cout << "Invalid choice." << std::endl;
pauseExecution();
}
}
}void displayStudyMenu() {
while (currentUser) {
clearScreen();
std::cout << "===== STUDY SESSIONS =====" << std::endl;
std::cout << "1. Start Study Session" << std::endl;
std::cout << "2. End Current Session" << std::endl;
std::cout << "3. View Study History" << std::endl;
std::cout << "4. Back to Main Menu" << std::endl;
switch (getIntInput("Enter your choice: ")) {
case 1: startStudySession(); break;
case 2: endStudySession(); break;
case 3: viewStudySessions(); break;
case 4: return;
default:
std::cout << "Invalid choice." << std::endl;
pauseExecution();
}
}
}void displayVisualizationMenu() {
while (currentUser) {
clearScreen();
std::cout << "===== VISUALIZATIONS =====" << std::endl;
std::cout << "1. Bar Chart - Time per Subject" << std::endl;
std::cout << "2. Pie Chart - Subject Distribution" << std::endl;
std::cout << "3. Back to Main Menu" << std::endl;
switch (getIntInput("Enter your choice: ")) {
case 1: createBarChart(); break;
case 2: createPieChart(); break;
case 3: return;
default:
std::cout << "Invalid choice." << std::endl;
pauseExecution();
}
}
}void displayTodoMenu() {
while (currentUser) {
clearScreen();
std::cout << "===== TO-DO LIST =====" << std::endl;
std::cout << "1. Add New Item" << std::endl;
std::cout << "2. View Items" << std::endl;
std::cout << "3. Mark Item as Complete" << std::endl;
std::cout << "4. Remove Item" << std::endl;
std::cout << "5. Back to Main Menu" << std::endl;
switch (getIntInput("Enter your choice: ")) {
case 1: addTodoItem(); break;
case 2: viewTodoList(); break;
case 3: markTodoItemComplete(); break;
case 4: removeTodoItem(); break;
case 5: return;
default:
std::cout << "Invalid choice." << std::endl;
pauseExecution();
}
}
}int main() {
std::system("mkdir -p data");
// Initialize SDL for visualizations
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
std::cerr << "Warning: SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
std::cout << "Continuing without graphical visualizations..." << std::endl;
}
if (TTF_Init() < 0) {
std::cerr << "Warning: SDL_ttf could not initialize! TTF_Error: " << TTF_GetError() << std::endl;
std::cout << "Text rendering in visualizations may be limited..." << std::endl;
}
displayLoginMenu();
// Clean up
for (auto user : users) delete user;
TTF_Quit();
SDL_Quit();
return 0;
}Cleans up resources before exiting:
This completes the detailed line-by-line explanation of the Study Tracker application with SDL visualization. The code implements a comprehensive study tracking system with features for managing study sessions, to-do items, and visualizing study data through bar charts and pie charts.