comparison src/anime.cpp @ 6:1d82f6e04d7d

Update: add first parts to the settings dialog
author Paper <mrpapersonic@gmail.com>
date Wed, 16 Aug 2023 00:49:17 -0400
parents 190ded9438c0
children 07a9095eaeed
comparison
equal deleted inserted replaced
5:51ae25154b70 6:1d82f6e04d7d
8 #include "anime.h" 8 #include "anime.h"
9 #include "date.h" 9 #include "date.h"
10 #include "time_utils.h" 10 #include "time_utils.h"
11 #include "information.h" 11 #include "information.h"
12 #include "ui_utils.h" 12 #include "ui_utils.h"
13 #include <iostream>
13 14
14 std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap = { 15 std::map<enum AnimeWatchingStatus, std::string> AnimeWatchingToStringMap = {
15 {CURRENT, "Watching"}, 16 {CURRENT, "Watching"},
16 {PLANNING, "Planning"}, 17 {PLANNING, "Planning"},
17 {COMPLETED, "Completed"}, 18 {COMPLETED, "Completed"},
72 audience_score = a.audience_score; 73 audience_score = a.audience_score;
73 synopsis = a.synopsis; 74 synopsis = a.synopsis;
74 duration = a.duration; 75 duration = a.duration;
75 } 76 }
76 77
78 std::string Anime::GetUserPreferredTitle() {
79 if (title.english.empty())
80 return title.romaji;
81 return title.english;
82 }
83
77 void AnimeList::Add(Anime& anime) { 84 void AnimeList::Add(Anime& anime) {
78 if (anime_id_to_anime.contains(anime.id)) 85 if (anime_id_to_anime.contains(anime.id))
79 return; 86 return;
80 anime_list.push_back(anime); 87 anime_list.push_back(anime);
81 anime_id_to_anime.emplace(anime.id, &anime); 88 anime_id_to_anime.emplace(anime.id, &anime);
153 const Anime& AnimeList::operator[](std::size_t index) const { 160 const Anime& AnimeList::operator[](std::size_t index) const {
154 return anime_list.at(index); 161 return anime_list.at(index);
155 } 162 }
156 163
157 /* ------------------------------------------------------------------------- */ 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 }
158 217
159 /* Thank you qBittorrent for having a great example of a 218 /* Thank you qBittorrent for having a great example of a
160 widget model. */ 219 widget model. */
161 AnimeListWidgetModel::AnimeListWidgetModel (QWidget* parent, AnimeList* alist) 220 AnimeListWidgetModel::AnimeListWidgetModel (QWidget* parent, AnimeList* alist)
162 : QAbstractListModel(parent) 221 : QAbstractListModel(parent)
179 switch (section) { 238 switch (section) {
180 case AL_TITLE: 239 case AL_TITLE:
181 return tr("Anime title"); 240 return tr("Anime title");
182 case AL_PROGRESS: 241 case AL_PROGRESS:
183 return tr("Progress"); 242 return tr("Progress");
243 case AL_EPISODES:
244 return tr("Episodes");
184 case AL_TYPE: 245 case AL_TYPE:
185 return tr("Type"); 246 return tr("Type");
186 case AL_SCORE: 247 case AL_SCORE:
187 return tr("Score"); 248 return tr("Score");
188 case AL_SEASON: 249 case AL_SEASON:
204 switch (section) { 265 switch (section) {
205 case AL_TITLE: 266 case AL_TITLE:
206 case AL_NOTES: 267 case AL_NOTES:
207 return QVariant(Qt::AlignLeft | Qt::AlignVCenter); 268 return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
208 case AL_PROGRESS: 269 case AL_PROGRESS:
270 case AL_EPISODES:
209 case AL_TYPE: 271 case AL_TYPE:
210 case AL_SCORE: 272 case AL_SCORE:
211 case AL_AVG_SCORE: 273 case AL_AVG_SCORE:
212 return QVariant(Qt::AlignCenter | Qt::AlignVCenter); 274 return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
213 case AL_SEASON: 275 case AL_SEASON:
227 } 289 }
228 290
229 QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const { 291 QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const {
230 if (!index.isValid()) 292 if (!index.isValid())
231 return QVariant(); 293 return QVariant();
232 if (role == Qt::DisplayRole) { 294 switch (role) {
233 switch (index.column()) { 295 case Qt::DisplayRole:
234 case AL_TITLE: 296 switch (index.column()) {
235 return QString::fromWCharArray(list[index.row()].title.english.c_str()); 297 case AL_TITLE:
236 case AL_PROGRESS: 298 return QString::fromUtf8(list[index.row()].GetUserPreferredTitle().c_str());
237 return list[index.row()].progress; 299 case AL_PROGRESS:
238 case AL_SCORE: 300 return QString::number(list[index.row()].progress) + "/" + QString::number(list[index.row()].episodes);
239 return list[index.row()].score; 301 case AL_EPISODES:
240 case AL_TYPE: 302 return list[index.row()].episodes;
241 return QString::fromStdString(AnimeFormatToStringMap[list[index.row()].type]); 303 case AL_SCORE:
242 case AL_SEASON: 304 return list[index.row()].score;
243 return QString::fromStdString(AnimeSeasonToStringMap[list[index.row()].season]) + " " + QString::number(list[index.row()].air_date.GetYear()); 305 case AL_TYPE:
244 case AL_AVG_SCORE: 306 return QString::fromStdString(AnimeFormatToStringMap[list[index.row()].type]);
245 return list[index.row()].audience_score; 307 case AL_SEASON:
246 case AL_STARTED: 308 return QString::fromStdString(AnimeSeasonToStringMap[list[index.row()].season]) + " " + QString::number(list[index.row()].air_date.GetYear());
247 return list[index.row()].started.GetAsQDate(); 309 case AL_AVG_SCORE:
248 case AL_COMPLETED: 310 return QString::number(list[index.row()].audience_score) + "%";
249 return list[index.row()].completed.GetAsQDate(); 311 case AL_STARTED:
250 case AL_UPDATED: { 312 return list[index.row()].started.GetAsQDate();
251 if (list[index.row()].updated == 0) 313 case AL_COMPLETED:
252 return QString("-"); 314 return list[index.row()].completed.GetAsQDate();
253 Time::Duration duration(Time::GetSystemTime() - list[index.row()].updated); 315 case AL_UPDATED: {
254 return QString::fromStdString(duration.AsRelativeString()); 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 "";
255 } 325 }
256 case AL_NOTES: 326 break;
257 return QString::fromWCharArray(list[index.row()].notes.c_str()); 327 case Qt::UserRole:
258 default: 328 switch (index.column()) {
259 return ""; 329 case AL_PROGRESS:
260 } 330 return list[index.row()].progress;
261 } else if (role == Qt::TextAlignmentRole) { 331 case AL_TYPE:
262 switch (index.column()) { 332 return list[index.row()].type;
263 case AL_TITLE: 333 case AL_SEASON:
264 case AL_NOTES: 334 return list[index.row()].air_date.GetAsQDate();
265 return QVariant(Qt::AlignLeft | Qt::AlignVCenter); 335 case AL_AVG_SCORE:
266 case AL_PROGRESS: 336 return list[index.row()].audience_score;
267 case AL_TYPE: 337 case AL_UPDATED:
268 case AL_SCORE: 338 return list[index.row()].updated;
269 case AL_AVG_SCORE: 339 default:
270 return QVariant(Qt::AlignCenter | Qt::AlignVCenter); 340 return data(index, Qt::DisplayRole);
271 case AL_SEASON: 341 }
272 case AL_STARTED: 342 break;
273 case AL_COMPLETED: 343 case Qt::TextAlignmentRole:
274 case AL_UPDATED: 344 switch (index.column()) {
275 return QVariant(Qt::AlignRight | Qt::AlignVCenter); 345 case AL_TITLE:
276 default: 346 case AL_NOTES:
277 break; 347 return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
278 } 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;
279 } 363 }
280 return QVariant(); 364 return QVariant();
281 } 365 }
282 366
283 void AnimeListWidgetModel::UpdateAnime(Anime& anime) { 367 void AnimeListWidgetModel::UpdateAnime(Anime& anime) {
284 int i = list.GetAnimeIndex(anime); 368 int i = list.GetAnimeIndex(anime);
285 emit dataChanged(index(i), index(i)); 369 emit dataChanged(index(i), index(i));
286 } 370 }
287
288 /* Most of this stuff is const and/or should be edited in the Information dialog
289
290 bool AnimeListWidgetModel::setData(const QModelIndex &index, const QVariant &value, int role) {
291 if (!index.isValid() || role != Qt::DisplayRole)
292 return false;
293
294 Anime* const anime = &list[index.row()];
295
296 switch (index.column()) {
297 case AL_TITLE:
298 break;
299 case AL_CATEGORY:
300 break;
301 default:
302 return false;
303 }
304
305 return true;
306 }
307 */
308 371
309 int AnimeListWidget::VisibleColumnsCount() const { 372 int AnimeListWidget::VisibleColumnsCount() const {
310 int count = 0; 373 int count = 0;
311 374
312 for (int i = 0, end = header()->count(); i < end; i++) 375 for (int i = 0, end = header()->count(); i < end; i++)
323 setColumnHidden(AnimeListWidgetModel::AL_TYPE, false); 386 setColumnHidden(AnimeListWidgetModel::AL_TYPE, false);
324 setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false); 387 setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false);
325 setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false); 388 setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false);
326 setColumnHidden(AnimeListWidgetModel::AL_SCORE, false); 389 setColumnHidden(AnimeListWidgetModel::AL_SCORE, false);
327 setColumnHidden(AnimeListWidgetModel::AL_TITLE, false); 390 setColumnHidden(AnimeListWidgetModel::AL_TITLE, false);
391 setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true);
328 setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true); 392 setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true);
329 setColumnHidden(AnimeListWidgetModel::AL_STARTED, true); 393 setColumnHidden(AnimeListWidgetModel::AL_STARTED, true);
330 setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true); 394 setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true);
331 setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true); 395 setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true);
332 setColumnHidden(AnimeListWidgetModel::AL_NOTES, true); 396 setColumnHidden(AnimeListWidgetModel::AL_NOTES, true);
368 menu->popup(QCursor::pos()); 432 menu->popup(QCursor::pos());
369 (void)(resetAction); 433 (void)(resetAction);
370 } 434 }
371 435
372 void AnimeListWidget::DisplayListMenu() { 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() {
373 /* throw out any other garbage */ 464 /* throw out any other garbage */
374 const QModelIndexList selected_items = selectionModel()->selectedRows(); 465 const QItemSelection selection = sort_model->mapSelectionToSource(selectionModel()->selection());
375 if (selected_items.size() != 1 || !selected_items.first().isValid()) { 466 if (!selection.indexes().first().isValid()) {
376 return; 467 return;
377 } 468 }
378 469
379 const QModelIndex index = model->index(selected_items.first().row()); 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();
380 Anime* anime = model->GetAnimeFromIndex(index); 475 Anime* anime = model->GetAnimeFromIndex(index);
381 if (!anime) { 476 if (!anime) {
382 return; 477 return;
383 } 478 }
384 479
385 }
386
387 void AnimeListWidget::ItemDoubleClicked() {
388 /* throw out any other garbage */
389 const QModelIndexList selected_items = selectionModel()->selectedRows();
390 if (selected_items.size() != 1 || !selected_items.first().isValid()) {
391 return;
392 }
393
394 /* TODO: after we implement our sort model, we have to use mapToSource here... */
395 const QModelIndex index = model->index(selected_items.first().row());
396 Anime* anime = model->GetAnimeFromIndex(index);
397 if (!anime) {
398 return;
399 }
400
401 InformationDialog* dialog = new InformationDialog(*anime, model, this); 480 InformationDialog* dialog = new InformationDialog(*anime, model, this);
402 481
403 dialog->show(); 482 dialog->show();
404 dialog->raise(); 483 dialog->raise();
405 dialog->activateWindow(); 484 dialog->activateWindow();
406 } 485 }
407 486
408 AnimeListWidget::AnimeListWidget(QWidget* parent, AnimeList* alist) 487 AnimeListWidget::AnimeListWidget(QWidget* parent, AnimeList* alist)
409 : QTreeView(parent) { 488 : QTreeView(parent) {
489 setItemDelegate(new AnimeListWidgetDelegate(this));
410 model = new AnimeListWidgetModel(parent, alist); 490 model = new AnimeListWidgetModel(parent, alist);
411 setModel(model); 491 sort_model = new AnimeListWidgetSortFilter(this);
492 sort_model->setSourceModel(model);
493 sort_model->setSortRole(Qt::UserRole);
494 setModel(sort_model);
412 setObjectName("listwidget"); 495 setObjectName("listwidget");
413 setStyleSheet("QTreeView#listwidget{border-top:0px;}"); 496 setStyleSheet("QTreeView#listwidget{border:0px;}");
414 setUniformRowHeights(true); 497 setUniformRowHeights(true);
415 setAllColumnsShowFocus(false); 498 setAllColumnsShowFocus(false);
416 setSortingEnabled(true); 499 setSortingEnabled(true);
417 setSelectionMode(QAbstractItemView::ExtendedSelection); 500 setSelectionMode(QAbstractItemView::ExtendedSelection);
418 setItemsExpandable(false); 501 setItemsExpandable(false);
436 // } 519 // }
437 } 520 }
438 521
439 AnimeListPage::AnimeListPage(QWidget* parent) : QTabWidget (parent) { 522 AnimeListPage::AnimeListPage(QWidget* parent) : QTabWidget (parent) {
440 setDocumentMode(false); 523 setDocumentMode(false);
524 setObjectName("animepage");
525 //setStyleSheet("QTabWidget#animepage{border-bottom:0px;border-left:0px;border-right:0px;}");
441 SyncAnimeList(); 526 SyncAnimeList();
442 for (AnimeList& list : anime_lists) { 527 for (AnimeList& list : anime_lists) {
443 addTab(new AnimeListWidget(this, &list), QString::fromWCharArray(list.name.c_str())); 528 addTab(new AnimeListWidget(this, &list), QString::fromUtf8(list.name.c_str()));
444 } 529 }
445 } 530 }
446 531
447 void AnimeListPage::SyncAnimeList() { 532 void AnimeListPage::SyncAnimeList() {
448 switch (session.config.service) { 533 switch (session.config.service) {
449 case ANILIST: { 534 case ANILIST: {
450 AniList anilist = AniList(); 535 AniList anilist = AniList();
451 anilist.Authorize();
452 session.config.anilist.user_id = anilist.GetUserId(session.config.anilist.username); 536 session.config.anilist.user_id = anilist.GetUserId(session.config.anilist.username);
453 FreeAnimeList(); 537 FreeAnimeList();
454 anilist.UpdateAnimeList(&anime_lists, session.config.anilist.user_id); 538 anilist.UpdateAnimeList(&anime_lists, session.config.anilist.user_id);
455 break; 539 break;
456 } 540 }
458 break; 542 break;
459 } 543 }
460 } 544 }
461 545
462 void AnimeListPage::FreeAnimeList() { 546 void AnimeListPage::FreeAnimeList() {
463 if (anime_lists.size() > 0) { 547 for (auto& list : anime_lists) {
464 /* FIXME: we may not need this, but to prevent memleaks 548 list.Clear();
465 we should keep it until we're sure we don't */ 549 }
466 for (auto& list : anime_lists) { 550 anime_lists.clear();
467 list.Clear();
468 }
469 anime_lists.clear();
470 }
471 } 551 }
472 552
473 int AnimeListPage::GetTotalAnimeAmount() { 553 int AnimeListPage::GetTotalAnimeAmount() {
474 int total = 0; 554 int total = 0;
475 for (auto& list : anime_lists) { 555 for (auto& list : anime_lists) {