Mercurial > minori
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) { |