7
|
1 /**
|
|
2 * anime_list.cpp: defines the anime list page
|
|
3 * and widgets.
|
|
4 *
|
|
5 * much of this file is based around
|
|
6 * Qt's original QTabWidget implementation, because
|
|
7 * I needed a somewhat native way to create a tabbed
|
|
8 * widget with only one subwidget that worked exactly
|
|
9 * like a native tabbed widget.
|
|
10 **/
|
|
11 #include <cmath>
|
|
12 #include <QStyledItemDelegate>
|
|
13 #include <QProgressBar>
|
|
14 #include <QShortcut>
|
|
15 #include <QHBoxLayout>
|
|
16 #include <QStylePainter>
|
|
17 #include <QMenu>
|
|
18 #include <QHeaderView>
|
|
19 #include "anilist.h"
|
|
20 #include "anime.h"
|
|
21 #include "anime_list.h"
|
|
22 #include "information.h"
|
|
23 #include "session.h"
|
|
24 #include "time_utils.h"
|
|
25
|
|
26 AnimeListWidgetDelegate::AnimeListWidgetDelegate(QObject* parent)
|
|
27 : QStyledItemDelegate (parent) {
|
|
28 }
|
|
29
|
|
30 QWidget* AnimeListWidgetDelegate::createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const {
|
|
31 // no edit 4 u
|
|
32 return nullptr;
|
|
33 }
|
|
34
|
|
35 void AnimeListWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
|
36 {
|
|
37 switch (index.column()) {
|
|
38 case AnimeListWidgetModel::AL_PROGRESS: {
|
|
39 const int progress = static_cast<int>(index.data(Qt::UserRole).toReal());
|
|
40 const int episodes = static_cast<int>(index.siblingAtColumn(AnimeListWidgetModel::AL_EPISODES).data(Qt::UserRole).toReal());
|
|
41
|
|
42 QStyleOptionViewItem customOption (option);
|
|
43 customOption.state.setFlag(QStyle::State_Enabled, true);
|
|
44
|
|
45 progress_bar.paint(painter, customOption, index.data().toString(), progress, episodes);
|
|
46 break;
|
|
47 }
|
|
48 default:
|
|
49 QStyledItemDelegate::paint(painter, option, index);
|
|
50 break;
|
|
51 }
|
|
52 }
|
|
53
|
|
54 AnimeListWidgetSortFilter::AnimeListWidgetSortFilter(QObject *parent)
|
|
55 : QSortFilterProxyModel(parent) {
|
|
56 }
|
|
57
|
|
58 bool AnimeListWidgetSortFilter::lessThan(const QModelIndex &l,
|
|
59 const QModelIndex &r) const {
|
|
60 QVariant left = sourceModel()->data(l, sortRole());
|
|
61 QVariant right = sourceModel()->data(r, sortRole());
|
|
62
|
|
63 switch (left.userType()) {
|
|
64 case QMetaType::Int:
|
|
65 case QMetaType::UInt:
|
|
66 case QMetaType::LongLong:
|
|
67 case QMetaType::ULongLong:
|
|
68 return left.toInt() < right.toInt();
|
|
69 case QMetaType::QDate:
|
|
70 return left.toDate() < right.toDate();
|
|
71 case QMetaType::QString:
|
|
72 default:
|
|
73 return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0;
|
|
74 }
|
|
75 }
|
|
76
|
|
77 AnimeListWidgetModel::AnimeListWidgetModel (QWidget* parent, AnimeList* alist)
|
|
78 : QAbstractListModel(parent)
|
|
79 , list(*alist) {
|
|
80 return;
|
|
81 }
|
|
82
|
|
83 int AnimeListWidgetModel::rowCount(const QModelIndex& parent) const {
|
|
84 return list.Size();
|
|
85 (void)(parent);
|
|
86 }
|
|
87
|
|
88 int AnimeListWidgetModel::columnCount(const QModelIndex& parent) const {
|
|
89 return NB_COLUMNS;
|
|
90 (void)(parent);
|
|
91 }
|
|
92
|
|
93 QVariant AnimeListWidgetModel::headerData(const int section, const Qt::Orientation orientation, const int role) const {
|
|
94 if (role == Qt::DisplayRole) {
|
|
95 switch (section) {
|
|
96 case AL_TITLE:
|
|
97 return tr("Anime title");
|
|
98 case AL_PROGRESS:
|
|
99 return tr("Progress");
|
|
100 case AL_EPISODES:
|
|
101 return tr("Episodes");
|
|
102 case AL_TYPE:
|
|
103 return tr("Type");
|
|
104 case AL_SCORE:
|
|
105 return tr("Score");
|
|
106 case AL_SEASON:
|
|
107 return tr("Season");
|
|
108 case AL_STARTED:
|
|
109 return tr("Date started");
|
|
110 case AL_COMPLETED:
|
|
111 return tr("Date completed");
|
|
112 case AL_NOTES:
|
|
113 return tr("Notes");
|
|
114 case AL_AVG_SCORE:
|
|
115 return tr("Average score");
|
|
116 case AL_UPDATED:
|
|
117 return tr("Last updated");
|
|
118 default:
|
|
119 return {};
|
|
120 }
|
|
121 } else if (role == Qt::TextAlignmentRole) {
|
|
122 switch (section) {
|
|
123 case AL_TITLE:
|
|
124 case AL_NOTES:
|
|
125 return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
|
|
126 case AL_PROGRESS:
|
|
127 case AL_EPISODES:
|
|
128 case AL_TYPE:
|
|
129 case AL_SCORE:
|
|
130 case AL_AVG_SCORE:
|
|
131 return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
|
|
132 case AL_SEASON:
|
|
133 case AL_STARTED:
|
|
134 case AL_COMPLETED:
|
|
135 case AL_UPDATED:
|
|
136 return QVariant(Qt::AlignRight | Qt::AlignVCenter);
|
|
137 default:
|
|
138 return QAbstractListModel::headerData(section, orientation, role);
|
|
139 }
|
|
140 }
|
|
141 return QAbstractListModel::headerData(section, orientation, role);
|
|
142 }
|
|
143
|
|
144 Anime* AnimeListWidgetModel::GetAnimeFromIndex(const QModelIndex& index) {
|
|
145 return (index.isValid()) ? &(list[index.row()]) : nullptr;
|
|
146 }
|
|
147
|
|
148 QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const {
|
|
149 if (!index.isValid())
|
|
150 return QVariant();
|
|
151 switch (role) {
|
|
152 case Qt::DisplayRole:
|
|
153 switch (index.column()) {
|
|
154 case AL_TITLE:
|
|
155 return QString::fromUtf8(list[index.row()].GetUserPreferredTitle().c_str());
|
|
156 case AL_PROGRESS:
|
|
157 return QString::number(list[index.row()].progress) + "/" + QString::number(list[index.row()].episodes);
|
|
158 case AL_EPISODES:
|
|
159 return list[index.row()].episodes;
|
|
160 case AL_SCORE:
|
|
161 return list[index.row()].score;
|
|
162 case AL_TYPE:
|
|
163 return QString::fromStdString(AnimeFormatToStringMap[list[index.row()].type]);
|
|
164 case AL_SEASON:
|
|
165 return QString::fromStdString(AnimeSeasonToStringMap[list[index.row()].season]) + " " + QString::number(list[index.row()].air_date.GetYear());
|
|
166 case AL_AVG_SCORE:
|
|
167 return QString::number(list[index.row()].audience_score) + "%";
|
|
168 case AL_STARTED:
|
|
169 return list[index.row()].started.GetAsQDate();
|
|
170 case AL_COMPLETED:
|
|
171 return list[index.row()].completed.GetAsQDate();
|
|
172 case AL_UPDATED: {
|
|
173 if (list[index.row()].updated == 0)
|
|
174 return QString("-");
|
|
175 Time::Duration duration(Time::GetSystemTime() - list[index.row()].updated);
|
|
176 return QString::fromUtf8(duration.AsRelativeString().c_str());
|
|
177 }
|
|
178 case AL_NOTES:
|
|
179 return QString::fromUtf8(list[index.row()].notes.c_str());
|
|
180 default:
|
|
181 return "";
|
|
182 }
|
|
183 break;
|
|
184 case Qt::UserRole:
|
|
185 switch (index.column()) {
|
|
186 case AL_PROGRESS:
|
|
187 return list[index.row()].progress;
|
|
188 case AL_TYPE:
|
|
189 return list[index.row()].type;
|
|
190 case AL_SEASON:
|
|
191 return list[index.row()].air_date.GetAsQDate();
|
|
192 case AL_AVG_SCORE:
|
|
193 return list[index.row()].audience_score;
|
|
194 case AL_UPDATED:
|
|
195 return list[index.row()].updated;
|
|
196 default:
|
|
197 return data(index, Qt::DisplayRole);
|
|
198 }
|
|
199 break;
|
|
200 case Qt::TextAlignmentRole:
|
|
201 switch (index.column()) {
|
|
202 case AL_TITLE:
|
|
203 case AL_NOTES:
|
|
204 return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
|
|
205 case AL_PROGRESS:
|
|
206 case AL_EPISODES:
|
|
207 case AL_TYPE:
|
|
208 case AL_SCORE:
|
|
209 case AL_AVG_SCORE:
|
|
210 return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
|
|
211 case AL_SEASON:
|
|
212 case AL_STARTED:
|
|
213 case AL_COMPLETED:
|
|
214 case AL_UPDATED:
|
|
215 return QVariant(Qt::AlignRight | Qt::AlignVCenter);
|
|
216 default:
|
|
217 break;
|
|
218 }
|
|
219 break;
|
|
220 }
|
|
221 return QVariant();
|
|
222 }
|
|
223
|
|
224 void AnimeListWidgetModel::UpdateAnime(Anime& anime) {
|
|
225 int i = list.GetAnimeIndex(anime);
|
|
226 emit dataChanged(index(i), index(i));
|
|
227 }
|
|
228
|
|
229 void AnimeListWidgetModel::Update(AnimeList const& new_list) {
|
|
230 list = AnimeList(new_list);
|
|
231 emit dataChanged(index(0), index(rowCount()));
|
|
232 }
|
|
233
|
|
234 int AnimeListWidget::VisibleColumnsCount() const {
|
|
235 int count = 0;
|
|
236
|
|
237 for (int i = 0, end = tree_view->header()->count(); i < end; i++)
|
|
238 {
|
|
239 if (!tree_view->isColumnHidden(i))
|
|
240 count++;
|
|
241 }
|
|
242
|
|
243 return count;
|
|
244 }
|
|
245
|
|
246 void AnimeListWidget::SetColumnDefaults() {
|
|
247 tree_view->setColumnHidden(AnimeListWidgetModel::AL_SEASON, false);
|
|
248 tree_view->setColumnHidden(AnimeListWidgetModel::AL_TYPE, false);
|
|
249 tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false);
|
|
250 tree_view->setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false);
|
|
251 tree_view->setColumnHidden(AnimeListWidgetModel::AL_SCORE, false);
|
|
252 tree_view->setColumnHidden(AnimeListWidgetModel::AL_TITLE, false);
|
|
253 tree_view->setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true);
|
|
254 tree_view->setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true);
|
|
255 tree_view->setColumnHidden(AnimeListWidgetModel::AL_STARTED, true);
|
|
256 tree_view->setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true);
|
|
257 tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true);
|
|
258 tree_view->setColumnHidden(AnimeListWidgetModel::AL_NOTES, true);
|
|
259 }
|
|
260
|
|
261 void AnimeListWidget::DisplayColumnHeaderMenu() {
|
|
262 QMenu *menu = new QMenu(this);
|
|
263 menu->setAttribute(Qt::WA_DeleteOnClose);
|
|
264 menu->setTitle(tr("Column visibility"));
|
|
265 menu->setToolTipsVisible(true);
|
|
266
|
|
267 for (int i = 0; i < AnimeListWidgetModel::NB_COLUMNS; i++) {
|
|
268 if (i == AnimeListWidgetModel::AL_TITLE)
|
|
269 continue;
|
|
270 const auto column_name = sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
|
|
271 QAction *action = menu->addAction(column_name, this, [this, i](const bool checked) {
|
|
272 if (!checked && (VisibleColumnsCount() <= 1))
|
|
273 return;
|
|
274
|
|
275 tree_view->setColumnHidden(i, !checked);
|
|
276
|
|
277 if (checked && (tree_view->columnWidth(i) <= 5))
|
|
278 tree_view->resizeColumnToContents(i);
|
|
279
|
|
280 // SaveSettings();
|
|
281 });
|
|
282 action->setCheckable(true);
|
|
283 action->setChecked(!tree_view->isColumnHidden(i));
|
|
284 }
|
|
285
|
|
286 menu->addSeparator();
|
|
287 QAction *resetAction = menu->addAction(tr("Reset to defaults"), this, [this]() {
|
|
288 for (int i = 0, count = tree_view->header()->count(); i < count; ++i)
|
|
289 {
|
|
290 SetColumnDefaults();
|
|
291 }
|
|
292 // SaveSettings();
|
|
293 });
|
|
294 menu->popup(QCursor::pos());
|
|
295 (void)(resetAction);
|
|
296 }
|
|
297
|
|
298 void AnimeListWidget::DisplayListMenu() {
|
|
299 QMenu *menu = new QMenu(this);
|
|
300 menu->setAttribute(Qt::WA_DeleteOnClose);
|
|
301 menu->setTitle(tr("Column visibility"));
|
|
302 menu->setToolTipsVisible(true);
|
|
303
|
|
304 const QItemSelection selection = sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection());
|
|
305 if (!selection.indexes().first().isValid()) {
|
|
306 return;
|
|
307 }
|
|
308
|
|
309 QAction* action = menu->addAction("Information", [this, selection]{
|
|
310 const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->index(selection.indexes().first().row());
|
|
311 Anime* anime = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index);
|
|
312 if (!anime) {
|
|
313 return;
|
|
314 }
|
|
315
|
|
316 InformationDialog* dialog = new InformationDialog(*anime, ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()), this);
|
|
317
|
|
318 dialog->show();
|
|
319 dialog->raise();
|
|
320 dialog->activateWindow();
|
|
321 });
|
|
322 menu->popup(QCursor::pos());
|
|
323 }
|
|
324
|
|
325 void AnimeListWidget::ItemDoubleClicked() {
|
|
326 /* throw out any other garbage */
|
|
327 const QItemSelection selection = sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection());
|
|
328 if (!selection.indexes().first().isValid()) {
|
|
329 return;
|
|
330 }
|
|
331
|
|
332 const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->index(selection.indexes().first().row());
|
|
333 Anime* anime = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index);
|
|
334 if (!anime) {
|
|
335 return;
|
|
336 }
|
|
337
|
|
338 InformationDialog* dialog = new InformationDialog(*anime, ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel()), this);
|
|
339
|
|
340 dialog->show();
|
|
341 dialog->raise();
|
|
342 dialog->activateWindow();
|
|
343 }
|
|
344
|
|
345 void AnimeListWidget::paintEvent(QPaintEvent*) {
|
|
346 QStylePainter p(this);
|
|
347
|
|
348 QStyleOptionTabWidgetFrame opt;
|
|
349 InitStyle(&opt);
|
|
350 opt.rect = panelRect;
|
|
351 p.drawPrimitive(QStyle::PE_FrameTabWidget, opt);
|
|
352 }
|
|
353
|
|
354 void AnimeListWidget::resizeEvent(QResizeEvent* e) {
|
|
355 QWidget::resizeEvent(e);
|
|
356 SetupLayout();
|
|
357 }
|
|
358
|
|
359 void AnimeListWidget::showEvent(QShowEvent*) {
|
|
360 SetupLayout();
|
|
361 }
|
|
362
|
|
363 void AnimeListWidget::InitBasicStyle(QStyleOptionTabWidgetFrame *option) const
|
|
364 {
|
|
365 if (!option)
|
|
366 return;
|
|
367
|
|
368 option->initFrom(this);
|
|
369 option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this);
|
|
370 option->shape = QTabBar::RoundedNorth;
|
|
371 option->tabBarRect = tab_bar->geometry();
|
|
372 }
|
|
373
|
|
374 void AnimeListWidget::InitStyle(QStyleOptionTabWidgetFrame *option) const
|
|
375 {
|
|
376 if (!option)
|
|
377 return;
|
|
378
|
|
379 InitBasicStyle(option);
|
|
380
|
|
381 //int exth = style()->pixelMetric(QStyle::PM_TabBarBaseHeight, nullptr, this);
|
|
382 QSize t(0, tree_view->frameWidth());
|
|
383 if (tab_bar->isVisibleTo(this)) {
|
|
384 t = tab_bar->sizeHint();
|
|
385 t.setWidth(width());
|
|
386 }
|
|
387 option->tabBarSize = t;
|
|
388
|
|
389 QRect selected_tab_rect = tab_bar->tabRect(tab_bar->currentIndex());
|
|
390 selected_tab_rect.moveTopLeft(selected_tab_rect.topLeft() + option->tabBarRect.topLeft());
|
|
391 option->selectedTabRect = selected_tab_rect;
|
|
392
|
|
393 option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this);
|
|
394 }
|
|
395
|
|
396 void AnimeListWidget::SetupLayout() {
|
|
397 QStyleOptionTabWidgetFrame option;
|
|
398 InitStyle(&option);
|
|
399
|
|
400 QRect tabRect = style()->subElementRect(QStyle::SE_TabWidgetTabBar, &option, this);
|
|
401 tabRect.setLeft(tabRect.left()+1);
|
|
402 panelRect = style()->subElementRect(QStyle::SE_TabWidgetTabPane, &option, this);
|
|
403 QRect contentsRect = style()->subElementRect(QStyle::SE_TabWidgetTabContents, &option, this);
|
|
404
|
|
405 tab_bar->setGeometry(tabRect);
|
|
406 tree_view->parentWidget()->setGeometry(contentsRect);
|
|
407 }
|
|
408
|
|
409 AnimeListWidget::AnimeListWidget(QWidget* parent)
|
|
410 : QWidget(parent) {
|
|
411 /* Tab bar */
|
|
412 tab_bar = new QTabBar(this);
|
|
413 tab_bar->setExpanding(false);
|
|
414 tab_bar->setDrawBase(false);
|
|
415
|
|
416 /* Tree view... */
|
|
417 QWidget* tree_widget = new QWidget(this);
|
|
418 tree_view = new QTreeView(tree_widget);
|
|
419 tree_view->setItemDelegate(new AnimeListWidgetDelegate(tree_view));
|
|
420 tree_view->setUniformRowHeights(true);
|
|
421 tree_view->setAllColumnsShowFocus(false);
|
|
422 tree_view->setSortingEnabled(true);
|
|
423 tree_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
424 tree_view->setItemsExpandable(false);
|
|
425 tree_view->setRootIsDecorated(false);
|
|
426 tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
427 tree_view->setFrameShape(QFrame::NoFrame);
|
|
428 QHBoxLayout* layout = new QHBoxLayout;
|
|
429 layout->addWidget(tree_view);
|
|
430 layout->setMargin(0);
|
|
431 tree_widget->setLayout(layout);
|
|
432 connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListWidget::ItemDoubleClicked);
|
|
433 connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayListMenu);
|
|
434
|
|
435 /* Enter & return keys */
|
|
436 connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut),
|
|
437 &QShortcut::activated, this, &AnimeListWidget::ItemDoubleClicked);
|
|
438
|
|
439 connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut),
|
|
440 &QShortcut::activated, this, &AnimeListWidget::ItemDoubleClicked);
|
|
441
|
|
442 tree_view->header()->setStretchLastSection(false);
|
|
443 tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
444 connect(tree_view->header(), &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayColumnHeaderMenu);
|
|
445
|
|
446 connect(tab_bar, &QTabBar::currentChanged, this, [this](int index){
|
|
447 if (index < sort_models.size())
|
|
448 tree_view->setModel(sort_models[index]);
|
|
449 });
|
|
450
|
|
451 setFocusPolicy(Qt::TabFocus);
|
|
452 setFocusProxy(tab_bar);
|
|
453 }
|
|
454
|
|
455 void AnimeListWidget::SyncAnimeList() {
|
|
456 switch (session.config.service) {
|
|
457 case ANILIST: {
|
|
458 session.config.anilist.user_id = AniList::GetUserId(session.config.anilist.username);
|
|
459 FreeAnimeList();
|
|
460 AniList::UpdateAnimeList(&anime_lists, session.config.anilist.user_id);
|
|
461 break;
|
|
462 }
|
|
463 default:
|
|
464 break;
|
|
465 }
|
|
466 for (unsigned int i = 0; i < anime_lists.size(); i++) {
|
|
467 tab_bar->addTab(QString::fromStdString(anime_lists[i].name));
|
|
468 AnimeListWidgetSortFilter* sort_model = new AnimeListWidgetSortFilter(tree_view);
|
|
469 sort_model->setSourceModel(new AnimeListWidgetModel(this, &anime_lists[i]));
|
|
470 sort_model->setSortRole(Qt::UserRole);
|
|
471 sort_model->setSortCaseSensitivity(Qt::CaseInsensitive);
|
|
472 sort_models.push_back(sort_model);
|
|
473 }
|
|
474 if (anime_lists.size() > 0)
|
|
475 tree_view->setModel(sort_models.at(0));
|
|
476 SetColumnDefaults();
|
|
477 SetupLayout();
|
|
478 }
|
|
479
|
|
480 void AnimeListWidget::FreeAnimeList() {
|
|
481 while (tab_bar->count())
|
|
482 tab_bar->removeTab(0);
|
|
483 while (sort_models.size()) {
|
|
484 delete sort_models[sort_models.size()-1];
|
|
485 sort_models.pop_back();
|
|
486 }
|
|
487 for (auto& list : anime_lists) {
|
|
488 list.Clear();
|
|
489 }
|
|
490 anime_lists.clear();
|
|
491 }
|
|
492
|
|
493 int AnimeListWidget::GetTotalAnimeAmount() {
|
|
494 int total = 0;
|
|
495 for (auto& list : anime_lists) {
|
|
496 total += list.Size();
|
|
497 }
|
|
498 return total;
|
|
499 }
|
|
500
|
|
501 int AnimeListWidget::GetTotalEpisodeAmount() {
|
|
502 /* FIXME: this also needs to take into account rewatches... */
|
|
503 int total = 0;
|
|
504 for (auto& list : anime_lists) {
|
|
505 for (auto& anime : list) {
|
|
506 total += anime.progress;
|
|
507 }
|
|
508 }
|
|
509 return total;
|
|
510 }
|
|
511
|
|
512 /* Returns the total watched amount in minutes. */
|
|
513 int AnimeListWidget::GetTotalWatchedAmount() {
|
|
514 int total = 0;
|
|
515 for (auto& list : anime_lists) {
|
|
516 for (auto& anime : list) {
|
|
517 total += anime.duration*anime.progress;
|
|
518 }
|
|
519 }
|
|
520 return total;
|
|
521 }
|
|
522
|
|
523 /* Returns the total planned amount in minutes.
|
|
524 Note that we should probably limit progress to the
|
|
525 amount of episodes, as AniList will let you
|
|
526 set episode counts up to 32768. But that should
|
|
527 rather be handled elsewhere. */
|
|
528 int AnimeListWidget::GetTotalPlannedAmount() {
|
|
529 int total = 0;
|
|
530 for (auto& list : anime_lists) {
|
|
531 for (auto& anime : list) {
|
|
532 total += anime.duration*(anime.episodes-anime.progress);
|
|
533 }
|
|
534 }
|
|
535 return total;
|
|
536 }
|
|
537
|
|
538 double AnimeListWidget::GetAverageScore() {
|
|
539 double avg = 0;
|
|
540 int amt = 0;
|
|
541 for (auto& list : anime_lists) {
|
|
542 for (auto& anime : list) {
|
|
543 avg += anime.score;
|
|
544 if (anime.score != 0)
|
|
545 amt++;
|
|
546 }
|
|
547 }
|
|
548 return avg/amt;
|
|
549 }
|
|
550
|
|
551 double AnimeListWidget::GetScoreDeviation() {
|
|
552 double squares_sum = 0, avg = GetAverageScore();
|
|
553 int amt = 0;
|
|
554 for (auto& list : anime_lists) {
|
|
555 for (auto& anime : list) {
|
|
556 if (anime.score != 0) {
|
|
557 squares_sum += std::pow((double)anime.score - avg, 2);
|
|
558 amt++;
|
|
559 }
|
|
560 }
|
|
561 }
|
|
562 return (amt > 0) ? std::sqrt(squares_sum / amt) : 0;
|
|
563 }
|
|
564
|
|
565 #include "moc_anime_list.cpp"
|