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