view src/gui/pages/statistics.cc @ 198:bc1ae1810855

dep/animia: switch from using classes to global functions the old idea was ok, but sort of hackish; this method doesn't use classes at all, and this way (especially important!) we can do wayland stuff AND x11 at the same time, which wasn't really possible without stupid workarounds in the other method
author Paper <mrpapersonic@gmail.com>
date Sun, 24 Dec 2023 02:59:42 -0500
parents 649786bae914
children 4d461ef7d424
line wrap: on
line source

#include "gui/pages/statistics.h"
#include "core/anime_db.h"
#include "core/strings.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);

	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));
	layout->addWidget(_anime_list.get());

	{
		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));

		/* Ew */
		{
			QWidget* score_graph_parent = new QWidget(score_dist_widget);
			QVBoxLayout* score_parent_layout = new QVBoxLayout(score_graph_parent);

			_score_distribution_graph.reset(new Graph<int>(score_graph_parent));
			score_parent_layout->addWidget(_score_distribution_graph.get());

			score_parent_layout->setSpacing(0);
			score_parent_layout->setContentsMargins(12, 0, 0, 0);

			score_graph_parent->setContentsMargins(3, 0, 0, 0);

			score_dist_layout->addWidget(score_graph_parent);
		}

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

		layout->addWidget(score_dist_widget);
	}


	_application.reset(new TextWidgets::LabelledSection(tr("Minori"), tr("Uptime:\nRequests made:"), "\n\n", this));
	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 :) */
static void add_time_segment(std::ostringstream& str, int x, const std::string_view& s, const std::string_view& p) {
	if (x > 0)
		str << x << ((x == 1) ? s : p);
}

std::string StatisticsPage::MinutesToDateString(const 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(const 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();
}

inline int GetTotalWithScore(const 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 << Strings::ToQString(SecondsToDateString(session.uptime() / 1000)) << '\n';
	ts << session.GetRequests();
	/* Application */
	// UiUtils::SetPlainTextEditData(application_data, QString::number(session.uptime() / 1000));
	_application->GetParagraph()->SetText(string);
}

#include "gui/pages/moc_statistics.cpp"