view src/gui/pages/statistics.cc @ 101:c537996cf67b

*: multitude of config changes 1. theme is now configurable from the settings menu (but you have to restart for it to apply) 2. config is now stored in an INI file, with no method of conversion from json (this repo is private-ish anyway)
author Paper <mrpapersonic@gmail.com>
date Fri, 03 Nov 2023 14:06:02 -0400
parents 2f373d48f889
children b315f3759c56
line wrap: on
line source

#include "gui/pages/statistics.h"
#include "core/anime_db.h"
#include "core/session.h"
#include "gui/pages/anime_list.h"
#include "gui/widgets/text.h"
#include "gui/widgets/graph.h"
#include <QString>
#include <QTextDocument>
#include <QTextStream>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>
#include <sstream>

StatisticsPage::StatisticsPage(QWidget* parent) : QFrame(parent) {
	QVBoxLayout* layout = new QVBoxLayout(this);

	setFrameShape(QFrame::Box);
	setFrameShadow(QFrame::Sunken);

	QPalette pal = QPalette();
	pal.setColor(QPalette::Window, pal.color(QPalette::Base));
	setPalette(pal);
	setAutoFillBackground(true);

	_anime_list.reset(new TextWidgets::LabelledSection(
	    tr("Anime list"),
	    tr("Anime count:\nEpisode count:\nTime spent watching:\nTime to complete:\nAverage score:\nScore deviation:"),
	    "", this));

	QWidget* score_dist_widget = new QWidget(this);
	QVBoxLayout* score_dist_layout = new QVBoxLayout(score_dist_widget);

	score_dist_layout->addWidget(new TextWidgets::Header(tr("Score distribution"), score_dist_widget));

	/* I have to explain myself here: I hate this :). This makes a widget as a layer to create a margin,
	   similar to what I do in text.cc with sections. I hate it and it should really be put into a separate
	   class, but whatever. */
	QWidget* content = new QWidget(score_dist_widget);
	QVBoxLayout* content_layout = new QVBoxLayout(content);
	_score_distribution_graph.reset(new Graph<int>(content));
	content_layout->addWidget(_score_distribution_graph.get());
	content_layout->setContentsMargins(0, 0, 0, 0);
	content->setContentsMargins(12, 0, 0, 0);

	score_dist_layout->addWidget(content);
	score_dist_layout->setContentsMargins(0, 0, 0, 0);

	_application.reset(new TextWidgets::LabelledSection(tr("Minori"), tr("Uptime:\nRequests made:"), "\n\n", this));

	layout->addWidget(_anime_list.get());
	layout->addWidget(score_dist_widget);
	layout->addWidget(_application.get());
	layout->addStretch();

	QTimer* timer = new QTimer(this);
	connect(timer, &QTimer::timeout, this, [this] {
		if (isVisible())
			UpdateStatistics();
	});
	timer->start(1000); // update statistics every second
}

void StatisticsPage::showEvent(QShowEvent*) {
	UpdateStatistics();
}

/* me abusing macros :) */
#define ADD_TIME_SEGMENT(r, x, s, p) \
	if (x > 0) \
	r << x << ((x == 1) ? s : p)
std::string StatisticsPage::MinutesToDateString(int minutes) {
	/* ew */
	int years = (minutes * (1 / 525949.2F));
	int months = (minutes * (1 / 43829.1F)) - (years * 12);
	int days = (minutes * (1 / 1440.0F)) - (years * 365.2425F) - (months * 30.436875F);
	int hours = (minutes * (1 / 60.0F)) - (years * 8765.82F) - (months * 730.485F) - (days * 24);
	int rest_minutes = (minutes) - (years * 525949.2F) - (months * 43829.1F) - (days * 1440) - (hours * 60);
	std::ostringstream return_stream;
	ADD_TIME_SEGMENT(return_stream, years, " year ", " years ");
	ADD_TIME_SEGMENT(return_stream, months, " month ", " months ");
	ADD_TIME_SEGMENT(return_stream, days, " day ", " days ");
	ADD_TIME_SEGMENT(return_stream, hours, " hour ", " hours ");
	if (rest_minutes > 0 || return_stream.str().size() == 0)
		return_stream << rest_minutes << ((rest_minutes == 1) ? " minute" : " minutes");
	return return_stream.str();
}

std::string StatisticsPage::SecondsToDateString(int sec) {
	/* this is all fairly unnecessary, but works:tm: */
	int years = sec * (1 / 31556952.0F);
	int months = sec * (1 / 2629746.0F) - (years * 12);
	int days = sec * (1 / 86400.0F) - (years * 365.2425F) - (months * 30.436875F);
	int hours = sec * (1 / 3600.0F) - (years * 8765.82F) - (months * 730.485F) - (days * 24);
	int minutes = (sec) * (1 / 60.0F) - (years * 525949.2F) - (months * 43829.1F) - (days * 1440.0F) - (hours * 60.0F);
	int seconds =
	    sec - (years * 31556952.0F) - (months * 2629746.0F) - (days * 86400.0F) - (hours * 3600.0F) - (minutes * 60.0F);
	std::ostringstream return_stream;
	ADD_TIME_SEGMENT(return_stream, years, " year ", " years ");
	ADD_TIME_SEGMENT(return_stream, months, " month ", " months ");
	ADD_TIME_SEGMENT(return_stream, days, " day ", " days ");
	ADD_TIME_SEGMENT(return_stream, hours, " hour ", " hours ");
	ADD_TIME_SEGMENT(return_stream, minutes, " minute ", " minutes ");
	if (seconds > 0 || return_stream.str().size() == 0)
		return_stream << seconds << ((seconds == 1) ? " second" : " seconds");
	return return_stream.str();
}
#undef ADD_TIME_SEGMENT

inline int GetTotalWithScore(int score) {
	int count = 0;
	for (const auto& item : Anime::db.items)
		if (item.second.IsInUserList() && item.second.GetUserScore() == score)
			count++;
	return count;
}

void StatisticsPage::UpdateStatistics() {
	/* Anime list */
	QString string = "";
	QTextStream ts(&string);
	ts << Anime::db.GetTotalAnimeAmount() << '\n';
	ts << Anime::db.GetTotalEpisodeAmount() << '\n';
	ts << MinutesToDateString(Anime::db.GetTotalWatchedAmount()).c_str() << '\n';
	ts << MinutesToDateString(Anime::db.GetTotalPlannedAmount()).c_str() << '\n';
	ts << Anime::db.GetAverageScore() << '\n';
	ts << Anime::db.GetScoreDeviation();
	_anime_list->GetParagraph()->SetText(string);

	_score_distribution_graph->Clear();
	for (int i = 10; i <= 100; i += 10)
		_score_distribution_graph->AddItem(i, GetTotalWithScore(i));

	string = "";
	ts << QString::fromUtf8(SecondsToDateString(session.uptime() / 1000).c_str()) << '\n';
	ts << session.GetRequests();
	/* Application */
	// UiUtils::SetPlainTextEditData(application_data, QString::number(session.uptime() / 1000));
	_application->GetParagraph()->SetText(string);
}

#include "gui/pages/moc_statistics.cpp"