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 |