Mercurial > minori
comparison src/gui/pages/anime_list.cc @ 273:f31305b9f60a
*: various code safety changes
this also makes the code build on Qt 5.7. I can't test it though
because I don't have it working... FAIL!
| author | Paper <paper@paper.us.eu.org> |
|---|---|
| date | Thu, 18 Apr 2024 16:53:17 -0400 |
| parents | 862d0d8619f6 |
| children | f6a756c19bfb |
comparison
equal
deleted
inserted
replaced
| 272:5437009cb10e | 273:f31305b9f60a |
|---|---|
| 25 #include <QMenu> | 25 #include <QMenu> |
| 26 #include <QProgressBar> | 26 #include <QProgressBar> |
| 27 #include <QShortcut> | 27 #include <QShortcut> |
| 28 #include <QStylePainter> | 28 #include <QStylePainter> |
| 29 #include <QStyledItemDelegate> | 29 #include <QStyledItemDelegate> |
| 30 #include <QThread> | 30 #include <QThreadPool> |
| 31 #include <QRunnable> | |
| 31 #include <QTreeView> | 32 #include <QTreeView> |
| 32 | 33 |
| 33 #include <set> | 34 #include <set> |
| 35 | |
| 36 AnimeListPageUpdateEntryThread::AnimeListPageUpdateEntryThread(AnimeListPage* parent) : QThread(parent) { | |
| 37 page_ = parent; | |
| 38 } | |
| 39 | |
| 40 void AnimeListPageUpdateEntryThread::AddToQueue(int id) { | |
| 41 if (isRunning()) | |
| 42 return; /* don't let us fuck ourselves */ | |
| 43 | |
| 44 queue_.push(id); | |
| 45 } | |
| 46 | |
| 47 /* processes the queue... */ | |
| 48 void AnimeListPageUpdateEntryThread::run() { | |
| 49 while (!queue_.empty() && !isInterruptionRequested()) { | |
| 50 Services::UpdateAnimeEntry(queue_.front()); | |
| 51 queue_.pop(); | |
| 52 } | |
| 53 page_->Refresh(); | |
| 54 } | |
| 34 | 55 |
| 35 AnimeListPageSortFilter::AnimeListPageSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { | 56 AnimeListPageSortFilter::AnimeListPageSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { |
| 36 } | 57 } |
| 37 | 58 |
| 38 bool AnimeListPageSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const { | 59 bool AnimeListPageSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const { |
| 109 case Qt::DisplayRole: | 130 case Qt::DisplayRole: |
| 110 switch (index.column()) { | 131 switch (index.column()) { |
| 111 case AL_TITLE: return Strings::ToQString(list[index.row()].GetUserPreferredTitle()); | 132 case AL_TITLE: return Strings::ToQString(list[index.row()].GetUserPreferredTitle()); |
| 112 case AL_PROGRESS: | 133 case AL_PROGRESS: |
| 113 return QString::number(list[index.row()].GetUserProgress()) + "/" + | 134 return QString::number(list[index.row()].GetUserProgress()) + "/" + |
| 114 QString::number(list[index.row()].GetEpisodes()); | 135 QString::number(list[index.row()].GetEpisodes()); |
| 115 case AL_EPISODES: return list[index.row()].GetEpisodes(); | 136 case AL_EPISODES: return list[index.row()].GetEpisodes(); |
| 116 case AL_SCORE: return Strings::ToQString(list[index.row()].GetUserPresentableScore()); | 137 case AL_SCORE: return Strings::ToQString(list[index.row()].GetUserPresentableScore()); |
| 117 case AL_TYPE: return Strings::ToQString(Translate::ToString(list[index.row()].GetFormat())); | 138 case AL_TYPE: return Strings::ToQString(Translate::ToString(list[index.row()].GetFormat())); |
| 118 case AL_SEASON: { | 139 case AL_SEASON: { |
| 119 std::optional<unsigned int> year = list[index.row()].GetAirDate().GetYear(); | 140 std::optional<unsigned int> year = list[index.row()].GetAirDate().GetYear(); |
| 120 if (!year) | 141 if (!year) |
| 121 return "Unknown Unknown"; | 142 return "Unknown Unknown"; |
| 122 return Strings::ToQString(Translate::ToLocalString(list[index.row()].GetSeason()) + " " + | 143 return Strings::ToQString(Translate::ToLocalString(list[index.row()].GetSeason()) + " " + |
| 123 Strings::ToUtf8String(year.value())); | 144 Strings::ToUtf8String(year.value())); |
| 124 } | 145 } |
| 125 case AL_AVG_SCORE: return QString::number(list[index.row()].GetAudienceScore()) + "%"; | 146 case AL_AVG_SCORE: return QString::number(list[index.row()].GetAudienceScore()) + "%"; |
| 126 case AL_STARTED: return list[index.row()].GetUserDateStarted().GetAsQDate(); | 147 case AL_STARTED: return list[index.row()].GetUserDateStarted().GetAsQDate(); |
| 127 case AL_COMPLETED: return list[index.row()].GetUserDateCompleted().GetAsQDate(); | 148 case AL_COMPLETED: return list[index.row()].GetUserDateCompleted().GetAsQDate(); |
| 128 case AL_UPDATED: { | 149 case AL_UPDATED: { |
| 217 tree_view->setColumnHidden(AnimeListPageModel::AL_UPDATED, true); | 238 tree_view->setColumnHidden(AnimeListPageModel::AL_UPDATED, true); |
| 218 tree_view->setColumnHidden(AnimeListPageModel::AL_NOTES, true); | 239 tree_view->setColumnHidden(AnimeListPageModel::AL_NOTES, true); |
| 219 } | 240 } |
| 220 | 241 |
| 221 void AnimeListPage::UpdateAnime(int id) { | 242 void AnimeListPage::UpdateAnime(int id) { |
| 222 QThread* thread = QThread::create([this, id] { Services::UpdateAnimeEntry(id); }); | 243 /* this ought to just add to the thread's buffer. */ |
| 223 | 244 if (update_entry_thread_.isRunning()) { |
| 224 connect(thread, &QThread::finished, this, &AnimeListPage::Refresh); | 245 update_entry_thread_.requestInterruption(); |
| 225 connect(thread, &QThread::finished, thread, &QThread::deleteLater); | 246 update_entry_thread_.wait(); |
| 226 | 247 } |
| 227 thread->start(); | 248 |
| 249 update_entry_thread_.AddToQueue(id); | |
| 250 update_entry_thread_.start(); | |
| 228 } | 251 } |
| 229 | 252 |
| 230 void AnimeListPage::RemoveAnime(int id) { | 253 void AnimeListPage::RemoveAnime(int id) { |
| 231 Anime::Anime& anime = Anime::db.items[id]; | 254 Anime::Anime& anime = Anime::db.items[id]; |
| 232 anime.RemoveFromUserList(); | 255 anime.RemoveFromUserList(); |
| 241 | 264 |
| 242 for (int i = 0; i < AnimeListPageModel::NB_COLUMNS; i++) { | 265 for (int i = 0; i < AnimeListPageModel::NB_COLUMNS; i++) { |
| 243 if (i == AnimeListPageModel::AL_TITLE) | 266 if (i == AnimeListPageModel::AL_TITLE) |
| 244 continue; | 267 continue; |
| 245 const auto column_name = | 268 const auto column_name = |
| 246 sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); | 269 sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); |
| 247 | 270 |
| 248 QAction* action = menu->addAction(column_name, this, [this, i](const bool checked) { | 271 QAction* action = menu->addAction(column_name, this, [this, i](const bool checked) { |
| 249 if (!checked && (VisibleColumnsCount() <= 1)) | 272 if (!checked && (VisibleColumnsCount() <= 1)) |
| 250 return; | 273 return; |
| 251 | 274 |
| 275 QMenu* menu = new QMenu(this); | 298 QMenu* menu = new QMenu(this); |
| 276 menu->setAttribute(Qt::WA_DeleteOnClose); | 299 menu->setAttribute(Qt::WA_DeleteOnClose); |
| 277 menu->setToolTipsVisible(true); | 300 menu->setToolTipsVisible(true); |
| 278 | 301 |
| 279 AnimeListPageModel* source_model = | 302 AnimeListPageModel* source_model = |
| 280 reinterpret_cast<AnimeListPageModel*>(sort_models[tab_bar->currentIndex()]->sourceModel()); | 303 reinterpret_cast<AnimeListPageModel*>(sort_models[tab_bar->currentIndex()]->sourceModel()); |
| 281 const QItemSelection selection = | 304 const QItemSelection selection = |
| 282 sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); | 305 sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); |
| 283 | 306 |
| 284 std::set<Anime::Anime*> animes; | 307 std::set<Anime::Anime*> animes; |
| 285 for (const auto& index : selection.indexes()) { | 308 for (const auto& index : selection.indexes()) { |
| 286 if (!index.isValid()) | 309 if (!index.isValid()) |
| 287 continue; | 310 continue; |
| 291 } | 314 } |
| 292 | 315 |
| 293 menu->addAction(tr("Information"), [this, animes] { | 316 menu->addAction(tr("Information"), [this, animes] { |
| 294 for (auto& anime : animes) { | 317 for (auto& anime : animes) { |
| 295 InformationDialog* dialog = new InformationDialog( | 318 InformationDialog* dialog = new InformationDialog( |
| 296 *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO, this); | 319 *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO, this); |
| 297 | 320 |
| 298 dialog->show(); | 321 dialog->show(); |
| 299 dialog->raise(); | 322 dialog->raise(); |
| 300 dialog->activateWindow(); | 323 dialog->activateWindow(); |
| 301 } | 324 } |
| 302 }); | 325 }); |
| 303 menu->addSeparator(); | 326 menu->addSeparator(); |
| 304 menu->addAction(tr("Edit"), [this, animes] { | 327 menu->addAction(tr("Edit"), [this, animes] { |
| 305 for (auto& anime : animes) { | 328 for (auto& anime : animes) { |
| 306 InformationDialog* dialog = new InformationDialog( | 329 InformationDialog* dialog = new InformationDialog( |
| 307 *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MY_LIST, this); | 330 *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MY_LIST, this); |
| 308 | 331 |
| 309 dialog->show(); | 332 dialog->show(); |
| 310 dialog->raise(); | 333 dialog->raise(); |
| 311 dialog->activateWindow(); | 334 dialog->activateWindow(); |
| 312 } | 335 } |
| 320 } | 343 } |
| 321 | 344 |
| 322 void AnimeListPage::ItemDoubleClicked() { | 345 void AnimeListPage::ItemDoubleClicked() { |
| 323 /* throw out any other garbage */ | 346 /* throw out any other garbage */ |
| 324 const QItemSelection selection = | 347 const QItemSelection selection = |
| 325 sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); | 348 sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection()); |
| 326 if (!selection.indexes().first().isValid()) { | 349 if (!selection.indexes().first().isValid()) { |
| 327 return; | 350 return; |
| 328 } | 351 } |
| 329 | 352 |
| 330 AnimeListPageModel* source_model = | 353 AnimeListPageModel* source_model = |
| 331 reinterpret_cast<AnimeListPageModel*>(sort_models[tab_bar->currentIndex()]->sourceModel()); | 354 reinterpret_cast<AnimeListPageModel*>(sort_models[tab_bar->currentIndex()]->sourceModel()); |
| 332 | 355 |
| 333 const QModelIndex index = source_model->index(selection.indexes().first().row()); | 356 const QModelIndex index = source_model->index(selection.indexes().first().row()); |
| 334 Anime::Anime* anime = source_model->GetAnimeFromIndex(index); | 357 Anime::Anime* anime = source_model->GetAnimeFromIndex(index); |
| 335 | 358 |
| 336 InformationDialog* dialog = new InformationDialog( | 359 InformationDialog* dialog = new InformationDialog( |
| 337 *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO, this); | 360 *anime, [this, anime] { UpdateAnime(anime->GetId()); }, InformationDialog::PAGE_MAIN_INFO, this); |
| 338 | 361 |
| 339 dialog->show(); | 362 dialog->show(); |
| 340 dialog->raise(); | 363 dialog->raise(); |
| 341 dialog->activateWindow(); | 364 dialog->activateWindow(); |
| 342 } | 365 } |
| 347 } | 370 } |
| 348 | 371 |
| 349 void AnimeListPage::RefreshTabs() { | 372 void AnimeListPage::RefreshTabs() { |
| 350 for (unsigned int i = 0; i < sort_models.size(); i++) | 373 for (unsigned int i = 0; i < sort_models.size(); i++) |
| 351 tab_bar->setTabText(i, Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + | 374 tab_bar->setTabText(i, Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + |
| 352 QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); | 375 QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); |
| 353 } | 376 } |
| 354 | 377 |
| 355 void AnimeListPage::Refresh() { | 378 void AnimeListPage::Refresh() { |
| 356 RefreshList(); | 379 RefreshList(); |
| 357 RefreshTabs(); | 380 RefreshTabs(); |
| 422 Refresh(); | 445 Refresh(); |
| 423 } | 446 } |
| 424 | 447 |
| 425 /* --------- QTabWidget replication end ---------- */ | 448 /* --------- QTabWidget replication end ---------- */ |
| 426 | 449 |
| 427 AnimeListPage::AnimeListPage(QWidget* parent) : QWidget(parent) { | 450 AnimeListPage::AnimeListPage(QWidget* parent) : QWidget(parent), update_entry_thread_(this) { |
| 428 /* Tab bar */ | 451 /* Tab bar */ |
| 429 tab_bar = new QTabBar(this); | 452 tab_bar = new QTabBar(this); |
| 430 tab_bar->setExpanding(false); | 453 tab_bar->setExpanding(false); |
| 431 tab_bar->setDrawBase(false); | 454 tab_bar->setDrawBase(false); |
| 432 | 455 |
| 443 tree_view->setContextMenuPolicy(Qt::CustomContextMenu); | 466 tree_view->setContextMenuPolicy(Qt::CustomContextMenu); |
| 444 tree_view->setFrameShape(QFrame::NoFrame); | 467 tree_view->setFrameShape(QFrame::NoFrame); |
| 445 | 468 |
| 446 for (unsigned int i = 0; i < sort_models.size(); i++) { | 469 for (unsigned int i = 0; i < sort_models.size(); i++) { |
| 447 tab_bar->addTab(Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + | 470 tab_bar->addTab(Strings::ToQString(Translate::ToString(Anime::ListStatuses[i])) + " (" + |
| 448 QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); | 471 QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")"); |
| 449 sort_models[i] = new AnimeListPageSortFilter(tree_view); | 472 sort_models[i] = new AnimeListPageSortFilter(tree_view); |
| 450 sort_models[i]->setSourceModel(new AnimeListPageModel(this, Anime::ListStatuses[i])); | 473 sort_models[i]->setSourceModel(new AnimeListPageModel(this, Anime::ListStatuses[i])); |
| 451 sort_models[i]->setSortRole(Qt::UserRole); | 474 sort_models[i]->setSortRole(Qt::UserRole); |
| 452 sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive); | 475 sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive); |
| 453 } | 476 } |
| 474 connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListPage::ItemDoubleClicked); | 497 connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListPage::ItemDoubleClicked); |
| 475 connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayListMenu); | 498 connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayListMenu); |
| 476 | 499 |
| 477 /* Enter & return keys */ | 500 /* Enter & return keys */ |
| 478 connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, | 501 connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, |
| 479 &AnimeListPage::ItemDoubleClicked); | 502 &AnimeListPage::ItemDoubleClicked); |
| 480 | 503 |
| 481 connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, | 504 connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this, |
| 482 &AnimeListPage::ItemDoubleClicked); | 505 &AnimeListPage::ItemDoubleClicked); |
| 483 | 506 |
| 484 tree_view->header()->setStretchLastSection(false); | 507 tree_view->header()->setStretchLastSection(false); |
| 485 tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu); | 508 tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu); |
| 486 connect(tree_view->header(), &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayColumnHeaderMenu); | 509 connect(tree_view->header(), &QWidget::customContextMenuRequested, this, &AnimeListPage::DisplayColumnHeaderMenu); |
| 487 | 510 |
