Mercurial > minori
comparison src/anime.cpp @ 7:07a9095eaeed
Update
Refactored some code, moved some around
| author | Paper <mrpapersonic@gmail.com> |
|---|---|
| date | Thu, 24 Aug 2023 23:11:38 -0400 |
| parents | 1d82f6e04d7d |
| children |
comparison
equal
deleted
inserted
replaced
| 6:1d82f6e04d7d | 7:07a9095eaeed |
|---|---|
| 1 /* | |
| 2 * anime.cpp: defining of custom anime-related | |
| 3 * datatypes & variables | |
| 4 */ | |
| 1 #include <chrono> | 5 #include <chrono> |
| 2 #include <string> | 6 #include <string> |
| 3 #include <vector> | 7 #include <vector> |
| 4 #include <cmath> | 8 #include <cmath> |
| 5 #include "window.h" | 9 #include <algorithm> |
| 6 #include "anilist.h" | 10 #include "anilist.h" |
| 7 #include "config.h" | |
| 8 #include "anime.h" | 11 #include "anime.h" |
| 9 #include "date.h" | 12 #include "date.h" |
| 10 #include "time_utils.h" | 13 #include "session.h" |
| 11 #include "information.h" | |
| 12 #include "ui_utils.h" | |
| 13 #include <iostream> | |
| 14 | 14 |
| 15 std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap = { | 15 std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap = { |
| 16 {CURRENT, "Watching"}, | 16 {CURRENT, "Watching"}, |
| 17 {PLANNING, "Planning"}, | 17 {PLANNING, "Planning"}, |
| 18 {COMPLETED, "Completed"}, | 18 {COMPLETED, "Completed"}, |
| 74 synopsis = a.synopsis; | 74 synopsis = a.synopsis; |
| 75 duration = a.duration; | 75 duration = a.duration; |
| 76 } | 76 } |
| 77 | 77 |
| 78 std::string Anime::GetUserPreferredTitle() { | 78 std::string Anime::GetUserPreferredTitle() { |
| 79 if (title.english.empty()) | 79 switch (session.config.anime_list.language) { |
| 80 return title.romaji; | 80 case NATIVE: |
| 81 return title.english; | 81 return (title.native.empty()) ? title.romaji : title.native; |
| 82 case ENGLISH: | |
| 83 return (title.english.empty()) ? title.romaji : title.english; | |
| 84 default: | |
| 85 return title.romaji; | |
| 86 } | |
| 87 } | |
| 88 | |
| 89 std::vector<std::string> Anime::GetTitleSynonyms() { | |
| 90 std::vector<std::string> result; | |
| 91 #define IN_VECTOR(v, k) \ | |
| 92 (std::count(v.begin(), v.end(), k)) | |
| 93 #define ADD_TO_SYNONYMS(v, k) \ | |
| 94 if (!k.empty() && !IN_VECTOR(v, k) && k != GetUserPreferredTitle()) v.push_back(k) | |
| 95 ADD_TO_SYNONYMS(result, title.english); | |
| 96 ADD_TO_SYNONYMS(result, title.romaji); | |
| 97 ADD_TO_SYNONYMS(result, title.native); | |
| 98 for (auto& synonym : synonyms) { | |
| 99 ADD_TO_SYNONYMS(result, synonym); | |
| 100 } | |
| 101 #undef ADD_TO_SYNONYMS | |
| 102 #undef IN_VECTOR | |
| 103 return result; | |
| 82 } | 104 } |
| 83 | 105 |
| 84 void AnimeList::Add(Anime& anime) { | 106 void AnimeList::Add(Anime& anime) { |
| 85 if (anime_id_to_anime.contains(anime.id)) | 107 if (anime_id_to_anime.contains(anime.id)) |
| 86 return; | 108 return; |
| 129 anime_list.push_back(Anime(l[i])); | 151 anime_list.push_back(Anime(l[i])); |
| 130 } | 152 } |
| 131 name = l.name; | 153 name = l.name; |
| 132 } | 154 } |
| 133 | 155 |
| 156 AnimeList& AnimeList::operator=(const AnimeList& l) { | |
| 157 if (this != &l) { | |
| 158 for (unsigned long long i = 0; i < l.Size(); i++) { | |
| 159 this->anime_list.push_back(Anime(l[i])); | |
| 160 } | |
| 161 this->name = l.name; | |
| 162 } | |
| 163 return *this; | |
| 164 } | |
| 165 | |
| 134 AnimeList::~AnimeList() { | 166 AnimeList::~AnimeList() { |
| 135 anime_list.clear(); | 167 anime_list.clear(); |
| 136 anime_list.shrink_to_fit(); | 168 anime_list.shrink_to_fit(); |
| 137 } | 169 } |
| 138 | 170 |
| 158 } | 190 } |
| 159 | 191 |
| 160 const Anime& AnimeList::operator[](std::size_t index) const { | 192 const Anime& AnimeList::operator[](std::size_t index) const { |
| 161 return anime_list.at(index); | 193 return anime_list.at(index); |
| 162 } | 194 } |
| 163 | |
| 164 /* ------------------------------------------------------------------------- */ | |
| 165 | |
| 166 AnimeListWidgetDelegate::AnimeListWidgetDelegate(QObject* parent) | |
| 167 : QStyledItemDelegate (parent) { | |
| 168 } | |
| 169 | |
| 170 QWidget *AnimeListWidgetDelegate::createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const | |
| 171 { | |
| 172 // LOL | |
| 173 return nullptr; | |
| 174 } | |
| 175 | |
| 176 void AnimeListWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const | |
| 177 { | |
| 178 switch (index.column()) { | |
| 179 case AnimeListWidgetModel::AL_PROGRESS: { | |
| 180 const int progress = static_cast<int>(index.data(Qt::UserRole).toReal()); | |
| 181 const int episodes = static_cast<int>(index.siblingAtColumn(AnimeListWidgetModel::AL_EPISODES).data(Qt::UserRole).toReal()); | |
| 182 | |
| 183 QStyleOptionViewItem customOption (option); | |
| 184 customOption.state.setFlag(QStyle::State_Enabled, true); | |
| 185 | |
| 186 progress_bar.paint(painter, customOption, index.data().toString(), progress, episodes); | |
| 187 break; | |
| 188 } | |
| 189 default: | |
| 190 QStyledItemDelegate::paint(painter, option, index); | |
| 191 break; | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 AnimeListWidgetSortFilter::AnimeListWidgetSortFilter(QObject *parent) | |
| 196 : QSortFilterProxyModel(parent) { | |
| 197 } | |
| 198 | |
| 199 bool AnimeListWidgetSortFilter::lessThan(const QModelIndex &l, | |
| 200 const QModelIndex &r) const { | |
| 201 QVariant left = sourceModel()->data(l, sortRole()); | |
| 202 QVariant right = sourceModel()->data(r, sortRole()); | |
| 203 | |
| 204 switch (left.userType()) { | |
| 205 case QMetaType::Int: | |
| 206 case QMetaType::UInt: | |
| 207 case QMetaType::LongLong: | |
| 208 case QMetaType::ULongLong: | |
| 209 return left.toInt() < right.toInt(); | |
| 210 case QMetaType::QDate: | |
| 211 return left.toDate() < right.toDate(); | |
| 212 case QMetaType::QString: | |
| 213 default: | |
| 214 return left.toString() < right.toString(); | |
| 215 } | |
| 216 } | |
| 217 | |
| 218 /* Thank you qBittorrent for having a great example of a | |
| 219 widget model. */ | |
| 220 AnimeListWidgetModel::AnimeListWidgetModel (QWidget* parent, AnimeList* alist) | |
| 221 : QAbstractListModel(parent) | |
| 222 , list(*alist) { | |
| 223 return; | |
| 224 } | |
| 225 | |
| 226 int AnimeListWidgetModel::rowCount(const QModelIndex& parent) const { | |
| 227 return list.Size(); | |
| 228 (void)(parent); | |
| 229 } | |
| 230 | |
| 231 int AnimeListWidgetModel::columnCount(const QModelIndex& parent) const { | |
| 232 return NB_COLUMNS; | |
| 233 (void)(parent); | |
| 234 } | |
| 235 | |
| 236 QVariant AnimeListWidgetModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { | |
| 237 if (role == Qt::DisplayRole) { | |
| 238 switch (section) { | |
| 239 case AL_TITLE: | |
| 240 return tr("Anime title"); | |
| 241 case AL_PROGRESS: | |
| 242 return tr("Progress"); | |
| 243 case AL_EPISODES: | |
| 244 return tr("Episodes"); | |
| 245 case AL_TYPE: | |
| 246 return tr("Type"); | |
| 247 case AL_SCORE: | |
| 248 return tr("Score"); | |
| 249 case AL_SEASON: | |
| 250 return tr("Season"); | |
| 251 case AL_STARTED: | |
| 252 return tr("Date started"); | |
| 253 case AL_COMPLETED: | |
| 254 return tr("Date completed"); | |
| 255 case AL_NOTES: | |
| 256 return tr("Notes"); | |
| 257 case AL_AVG_SCORE: | |
| 258 return tr("Average score"); | |
| 259 case AL_UPDATED: | |
| 260 return tr("Last updated"); | |
| 261 default: | |
| 262 return {}; | |
| 263 } | |
| 264 } else if (role == Qt::TextAlignmentRole) { | |
| 265 switch (section) { | |
| 266 case AL_TITLE: | |
| 267 case AL_NOTES: | |
| 268 return QVariant(Qt::AlignLeft | Qt::AlignVCenter); | |
| 269 case AL_PROGRESS: | |
| 270 case AL_EPISODES: | |
| 271 case AL_TYPE: | |
| 272 case AL_SCORE: | |
| 273 case AL_AVG_SCORE: | |
| 274 return QVariant(Qt::AlignCenter | Qt::AlignVCenter); | |
| 275 case AL_SEASON: | |
| 276 case AL_STARTED: | |
| 277 case AL_COMPLETED: | |
| 278 case AL_UPDATED: | |
| 279 return QVariant(Qt::AlignRight | Qt::AlignVCenter); | |
| 280 default: | |
| 281 return QAbstractListModel::headerData(section, orientation, role); | |
| 282 } | |
| 283 } | |
| 284 return QAbstractListModel::headerData(section, orientation, role); | |
| 285 } | |
| 286 | |
| 287 Anime* AnimeListWidgetModel::GetAnimeFromIndex(const QModelIndex& index) { | |
| 288 return (index.isValid()) ? &(list[index.row()]) : nullptr; | |
| 289 } | |
| 290 | |
| 291 QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const { | |
| 292 if (!index.isValid()) | |
| 293 return QVariant(); | |
| 294 switch (role) { | |
| 295 case Qt::DisplayRole: | |
| 296 switch (index.column()) { | |
| 297 case AL_TITLE: | |
| 298 return QString::fromUtf8(list[index.row()].GetUserPreferredTitle().c_str()); | |
| 299 case AL_PROGRESS: | |
| 300 return QString::number(list[index.row()].progress) + "/" + QString::number(list[index.row()].episodes); | |
| 301 case AL_EPISODES: | |
| 302 return list[index.row()].episodes; | |
| 303 case AL_SCORE: | |
| 304 return list[index.row()].score; | |
| 305 case AL_TYPE: | |
| 306 return QString::fromStdString(AnimeFormatToStringMap[list[index.row()].type]); | |
| 307 case AL_SEASON: | |
| 308 return QString::fromStdString(AnimeSeasonToStringMap[list[index.row()].season]) + " " + QString::number(list[index.row()].air_date.GetYear()); | |
| 309 case AL_AVG_SCORE: | |
| 310 return QString::number(list[index.row()].audience_score) + "%"; | |
| 311 case AL_STARTED: | |
| 312 return list[index.row()].started.GetAsQDate(); | |
| 313 case AL_COMPLETED: | |
| 314 return list[index.row()].completed.GetAsQDate(); | |
| 315 case AL_UPDATED: { | |
| 316 if (list[index.row()].updated == 0) | |
| 317 return QString("-"); | |
| 318 Time::Duration duration(Time::GetSystemTime() - list[index.row()].updated); | |
| 319 return QString::fromUtf8(duration.AsRelativeString().c_str()); | |
| 320 } | |
| 321 case AL_NOTES: | |
| 322 return QString::fromUtf8(list[index.row()].notes.c_str()); | |
| 323 default: | |
| 324 return ""; | |
| 325 } | |
| 326 break; | |
| 327 case Qt::UserRole: | |
| 328 switch (index.column()) { | |
| 329 case AL_PROGRESS: | |
| 330 return list[index.row()].progress; | |
| 331 case AL_TYPE: | |
| 332 return list[index.row()].type; | |
| 333 case AL_SEASON: | |
| 334 return list[index.row()].air_date.GetAsQDate(); | |
| 335 case AL_AVG_SCORE: | |
| 336 return list[index.row()].audience_score; | |
| 337 case AL_UPDATED: | |
| 338 return list[index.row()].updated; | |
| 339 default: | |
| 340 return data(index, Qt::DisplayRole); | |
| 341 } | |
| 342 break; | |
| 343 case Qt::TextAlignmentRole: | |
| 344 switch (index.column()) { | |
| 345 case AL_TITLE: | |
| 346 case AL_NOTES: | |
| 347 return QVariant(Qt::AlignLeft | Qt::AlignVCenter); | |
| 348 case AL_PROGRESS: | |
| 349 case AL_EPISODES: | |
| 350 case AL_TYPE: | |
| 351 case AL_SCORE: | |
| 352 case AL_AVG_SCORE: | |
| 353 return QVariant(Qt::AlignCenter | Qt::AlignVCenter); | |
| 354 case AL_SEASON: | |
| 355 case AL_STARTED: | |
| 356 case AL_COMPLETED: | |
| 357 case AL_UPDATED: | |
| 358 return QVariant(Qt::AlignRight | Qt::AlignVCenter); | |
| 359 default: | |
| 360 break; | |
| 361 } | |
| 362 break; | |
| 363 } | |
| 364 return QVariant(); | |
| 365 } | |
| 366 | |
| 367 void AnimeListWidgetModel::UpdateAnime(Anime& anime) { | |
| 368 int i = list.GetAnimeIndex(anime); | |
| 369 emit dataChanged(index(i), index(i)); | |
| 370 } | |
| 371 | |
| 372 int AnimeListWidget::VisibleColumnsCount() const { | |
| 373 int count = 0; | |
| 374 | |
| 375 for (int i = 0, end = header()->count(); i < end; i++) | |
| 376 { | |
| 377 if (!isColumnHidden(i)) | |
| 378 count++; | |
| 379 } | |
| 380 | |
| 381 return count; | |
| 382 } | |
| 383 | |
| 384 void AnimeListWidget::SetColumnDefaults() { | |
| 385 setColumnHidden(AnimeListWidgetModel::AL_SEASON, false); | |
| 386 setColumnHidden(AnimeListWidgetModel::AL_TYPE, false); | |
| 387 setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false); | |
| 388 setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false); | |
| 389 setColumnHidden(AnimeListWidgetModel::AL_SCORE, false); | |
| 390 setColumnHidden(AnimeListWidgetModel::AL_TITLE, false); | |
| 391 setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true); | |
| 392 setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true); | |
| 393 setColumnHidden(AnimeListWidgetModel::AL_STARTED, true); | |
| 394 setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true); | |
| 395 setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true); | |
| 396 setColumnHidden(AnimeListWidgetModel::AL_NOTES, true); | |
| 397 } | |
| 398 | |
| 399 void AnimeListWidget::DisplayColumnHeaderMenu() { | |
| 400 QMenu *menu = new QMenu(this); | |
| 401 menu->setAttribute(Qt::WA_DeleteOnClose); | |
| 402 menu->setTitle(tr("Column visibility")); | |
| 403 menu->setToolTipsVisible(true); | |
| 404 | |
| 405 for (int i = 0; i < AnimeListWidgetModel::NB_COLUMNS; i++) { | |
| 406 if (i == AnimeListWidgetModel::AL_TITLE) | |
| 407 continue; | |
| 408 const auto column_name = model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); | |
| 409 QAction *action = menu->addAction(column_name, this, [this, i](const bool checked) { | |
| 410 if (!checked && (VisibleColumnsCount() <= 1)) | |
| 411 return; | |
| 412 | |
| 413 setColumnHidden(i, !checked); | |
| 414 | |
| 415 if (checked && (columnWidth(i) <= 5)) | |
| 416 resizeColumnToContents(i); | |
| 417 | |
| 418 // SaveSettings(); | |
| 419 }); | |
| 420 action->setCheckable(true); | |
| 421 action->setChecked(!isColumnHidden(i)); | |
| 422 } | |
| 423 | |
| 424 menu->addSeparator(); | |
| 425 QAction *resetAction = menu->addAction(tr("Reset to defaults"), this, [this]() { | |
| 426 for (int i = 0, count = header()->count(); i < count; ++i) | |
| 427 { | |
| 428 SetColumnDefaults(); | |
| 429 } | |
| 430 // SaveSettings(); | |
| 431 }); | |
| 432 menu->popup(QCursor::pos()); | |
| 433 (void)(resetAction); | |
| 434 } | |
| 435 | |
| 436 void AnimeListWidget::DisplayListMenu() { | |
| 437 QMenu *menu = new QMenu(this); | |
| 438 menu->setAttribute(Qt::WA_DeleteOnClose); | |
| 439 menu->setTitle(tr("Column visibility")); | |
| 440 menu->setToolTipsVisible(true); | |
| 441 | |
| 442 const QItemSelection selection = sort_model->mapSelectionToSource(selectionModel()->selection()); | |
| 443 if (!selection.indexes().first().isValid()) { | |
| 444 return; | |
| 445 } | |
| 446 | |
| 447 QAction* action = menu->addAction("Information", [this, selection]{ | |
| 448 const QModelIndex index = model->index(selection.indexes().first().row()); | |
| 449 Anime* anime = model->GetAnimeFromIndex(index); | |
| 450 if (!anime) { | |
| 451 return; | |
| 452 } | |
| 453 | |
| 454 InformationDialog* dialog = new InformationDialog(*anime, model, this); | |
| 455 | |
| 456 dialog->show(); | |
| 457 dialog->raise(); | |
| 458 dialog->activateWindow(); | |
| 459 }); | |
| 460 menu->popup(QCursor::pos()); | |
| 461 } | |
| 462 | |
| 463 void AnimeListWidget::ItemDoubleClicked() { | |
| 464 /* throw out any other garbage */ | |
| 465 const QItemSelection selection = sort_model->mapSelectionToSource(selectionModel()->selection()); | |
| 466 if (!selection.indexes().first().isValid()) { | |
| 467 return; | |
| 468 } | |
| 469 | |
| 470 const QModelIndex index = model->index(selection.indexes().first().row()); | |
| 471 const QString title = index.siblingAtColumn(AnimeListWidgetModel::AL_TITLE).data(Qt::UserRole).toString(); | |
| 472 QMessageBox box; | |
| 473 box.setText(QString::number(title.size())); | |
| 474 box.exec(); | |
| 475 Anime* anime = model->GetAnimeFromIndex(index); | |
| 476 if (!anime) { | |
| 477 return; | |
| 478 } | |
| 479 | |
| 480 InformationDialog* dialog = new InformationDialog(*anime, model, this); | |
| 481 | |
| 482 dialog->show(); | |
| 483 dialog->raise(); | |
| 484 dialog->activateWindow(); | |
| 485 } | |
| 486 | |
| 487 AnimeListWidget::AnimeListWidget(QWidget* parent, AnimeList* alist) | |
| 488 : QTreeView(parent) { | |
| 489 setItemDelegate(new AnimeListWidgetDelegate(this)); | |
| 490 model = new AnimeListWidgetModel(parent, alist); | |
| 491 sort_model = new AnimeListWidgetSortFilter(this); | |
| 492 sort_model->setSourceModel(model); | |
| 493 sort_model->setSortRole(Qt::UserRole); | |
| 494 setModel(sort_model); | |
| 495 setObjectName("listwidget"); | |
| 496 setStyleSheet("QTreeView#listwidget{border:0px;}"); | |
| 497 setUniformRowHeights(true); | |
| 498 setAllColumnsShowFocus(false); | |
| 499 setSortingEnabled(true); | |
| 500 setSelectionMode(QAbstractItemView::ExtendedSelection); | |
| 501 setItemsExpandable(false); | |
| 502 setRootIsDecorated(false); | |
| 503 setContextMenuPolicy(Qt::CustomContextMenu); | |
| 504 connect(this, &QAbstractItemView::doubleClicked, this, &AnimeListWidget::ItemDoubleClicked); | |
| 505 connect(this, &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayListMenu); | |
| 506 | |
| 507 /* Enter & return keys */ | |
| 508 connect(new QShortcut(Qt::Key_Return, this, nullptr, nullptr, Qt::WidgetShortcut), | |
| 509 &QShortcut::activated, this, &AnimeListWidget::ItemDoubleClicked); | |
| 510 | |
| 511 connect(new QShortcut(Qt::Key_Enter, this, nullptr, nullptr, Qt::WidgetShortcut), | |
| 512 &QShortcut::activated, this, &AnimeListWidget::ItemDoubleClicked); | |
| 513 | |
| 514 header()->setStretchLastSection(false); | |
| 515 header()->setContextMenuPolicy(Qt::CustomContextMenu); | |
| 516 connect(header(), &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayColumnHeaderMenu); | |
| 517 // if(!session.config.anime_list.columns) { | |
| 518 SetColumnDefaults(); | |
| 519 // } | |
| 520 } | |
| 521 | |
| 522 AnimeListPage::AnimeListPage(QWidget* parent) : QTabWidget (parent) { | |
| 523 setDocumentMode(false); | |
| 524 setObjectName("animepage"); | |
| 525 //setStyleSheet("QTabWidget#animepage{border-bottom:0px;border-left:0px;border-right:0px;}"); | |
| 526 SyncAnimeList(); | |
| 527 for (AnimeList& list : anime_lists) { | |
| 528 addTab(new AnimeListWidget(this, &list), QString::fromUtf8(list.name.c_str())); | |
| 529 } | |
| 530 } | |
| 531 | |
| 532 void AnimeListPage::SyncAnimeList() { | |
| 533 switch (session.config.service) { | |
| 534 case ANILIST: { | |
| 535 AniList anilist = AniList(); | |
| 536 session.config.anilist.user_id = anilist.GetUserId(session.config.anilist.username); | |
| 537 FreeAnimeList(); | |
| 538 anilist.UpdateAnimeList(&anime_lists, session.config.anilist.user_id); | |
| 539 break; | |
| 540 } | |
| 541 default: | |
| 542 break; | |
| 543 } | |
| 544 } | |
| 545 | |
| 546 void AnimeListPage::FreeAnimeList() { | |
| 547 for (auto& list : anime_lists) { | |
| 548 list.Clear(); | |
| 549 } | |
| 550 anime_lists.clear(); | |
| 551 } | |
| 552 | |
| 553 int AnimeListPage::GetTotalAnimeAmount() { | |
| 554 int total = 0; | |
| 555 for (auto& list : anime_lists) { | |
| 556 total += list.Size(); | |
| 557 } | |
| 558 return total; | |
| 559 } | |
| 560 | |
| 561 int AnimeListPage::GetTotalEpisodeAmount() { | |
| 562 /* FIXME: this also needs to take into account rewatches... */ | |
| 563 int total = 0; | |
| 564 for (auto& list : anime_lists) { | |
| 565 for (auto& anime : list) { | |
| 566 total += anime.progress; | |
| 567 } | |
| 568 } | |
| 569 return total; | |
| 570 } | |
| 571 | |
| 572 /* Returns the total watched amount in minutes. */ | |
| 573 int AnimeListPage::GetTotalWatchedAmount() { | |
| 574 int total = 0; | |
| 575 for (auto& list : anime_lists) { | |
| 576 for (auto& anime : list) { | |
| 577 total += anime.duration*anime.progress; | |
| 578 } | |
| 579 } | |
| 580 return total; | |
| 581 } | |
| 582 | |
| 583 /* Returns the total planned amount in minutes. | |
| 584 Note that we should probably limit progress to the | |
| 585 amount of episodes, as AniList will let you | |
| 586 set episode counts up to 32768. But that should | |
| 587 rather be handled elsewhere. */ | |
| 588 int AnimeListPage::GetTotalPlannedAmount() { | |
| 589 int total = 0; | |
| 590 for (auto& list : anime_lists) { | |
| 591 for (auto& anime : list) { | |
| 592 total += anime.duration*(anime.episodes-anime.progress); | |
| 593 } | |
| 594 } | |
| 595 return total; | |
| 596 } | |
| 597 | |
| 598 double AnimeListPage::GetAverageScore() { | |
| 599 double avg = 0; | |
| 600 int amt = 0; | |
| 601 for (auto& list : anime_lists) { | |
| 602 for (auto& anime : list) { | |
| 603 avg += anime.score; | |
| 604 if (anime.score != 0) | |
| 605 amt++; | |
| 606 } | |
| 607 } | |
| 608 return avg/amt; | |
| 609 } | |
| 610 | |
| 611 double AnimeListPage::GetScoreDeviation() { | |
| 612 double squares_sum = 0, avg = GetAverageScore(); | |
| 613 int amt = 0; | |
| 614 for (auto& list : anime_lists) { | |
| 615 for (auto& anime : list) { | |
| 616 if (anime.score != 0) { | |
| 617 squares_sum += std::pow((double)anime.score - avg, 2); | |
| 618 amt++; | |
| 619 } | |
| 620 } | |
| 621 } | |
| 622 return (amt > 0) ? std::sqrt(squares_sum / amt) : 0; | |
| 623 } | |
| 624 | |
| 625 #include "moc_anime.cpp" |
