From 5511c195766de625bf6c4bf39950940814c27aad Mon Sep 17 00:00:00 2001 From: ConfuSomu Date: Wed, 3 Jan 2024 19:20:26 +0100 Subject: Move activity list to a tab widget This allows us to have more tabs in the future with additional information, such as information about the Actor or even other activity views. This commit was quite a lot of work and refactoring! --- src/mainwindow.cpp | 208 +++++++------------------------------- src/mainwindow.h | 35 ++----- src/mainwindow.ui | 49 +-------- src/widgets/tab_activity_list.cpp | 182 +++++++++++++++++++++++++++++++++ src/widgets/tab_activity_list.h | 69 +++++++++++++ src/widgets/tab_activity_list.ui | 63 ++++++++++++ 6 files changed, 362 insertions(+), 244 deletions(-) create mode 100644 src/widgets/tab_activity_list.cpp create mode 100644 src/widgets/tab_activity_list.h create mode 100644 src/widgets/tab_activity_list.ui (limited to 'src') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 04182ec..023119f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,18 +1,13 @@ #include "mainwindow.h" #include "./ui_mainwindow.h" #include "src/archive/base_archive.h" -#include "src/finddialog.h" -#include "src/list_item.h" #include "src/command_line.h" #include "src/settingsdialog.h" -#include "src/net/instance.h" +#include "src/widgets/tab_activity_list.h" #include #include #include -#include -#include -#include #include MainWindow::MainWindow(QWidget *parent) @@ -20,11 +15,7 @@ MainWindow::MainWindow(QWidget *parent) , ui(new Ui::MainWindow) { ui->setupUi(this); - status_info_widget = new StatusInfoWidget(this); - ui->gridLayout->addWidget(status_info_widget, 1, 1); - connect(ui->buttonCopy, &QPushButton::clicked, ui->actionCopy_status, &QAction::trigger); - connect(ui->buttonRandom, &QPushButton::clicked, ui->actionRandom_status, &QAction::trigger); - connect(ui->buttonSearch, &QPushButton::clicked, ui->actionFind, &QAction::trigger); + create_initial_tabs(); connect(&archive_thread_watcher, &QFutureWatcher::finished, this, &MainWindow::archive_thread_watcher_done); } @@ -33,6 +24,35 @@ MainWindow::~MainWindow() delete ui; } +void MainWindow::create_initial_tabs() { + activity_list_tab = new TabActivityList(&data_archive); + ui->tabWidget->addTab(activity_list_tab, tr("Activity List")); + activity_list_tab->view_actions = { + .actionAll_toots = ui->actionAll_toots, + .actionPublic_toots = ui->actionPublic_toots, + .actionUnlisted_toots = ui->actionUnlisted_toots, + .actionPrivate_toots = ui->actionPrivate_toots, + .actionDirect_messages = ui->actionDirect_messages, + .actionOnly_with_attachment = ui->actionOnly_with_attachment + }; + + connect(ui->actionAll_toots, &QAction::triggered, activity_list_tab, &TabActivityList::actionAll_toots_triggered); + connect(ui->actionPublic_toots, &QAction::triggered, activity_list_tab, &TabActivityList::actionPublic_toots_triggered); + connect(ui->actionUnlisted_toots, &QAction::triggered, activity_list_tab, &TabActivityList::actionUnlisted_toots_triggered); + connect(ui->actionPrivate_toots, &QAction::triggered, activity_list_tab, &TabActivityList::actionPrivate_toots_triggered); + connect(ui->actionDirect_messages, &QAction::triggered, activity_list_tab, &TabActivityList::actionDirect_messages_triggered); + connect(ui->actionOnly_with_attachment, &QAction::triggered, activity_list_tab, &TabActivityList::actionOnly_with_attachment_triggered); + connect(ui->actionReblogs, &QAction::triggered, activity_list_tab, &TabActivityList::actionReblogs_triggered); + connect(ui->actionRandom_status, &QAction::triggered, activity_list_tab, &TabActivityList::on_buttonRandom_clicked); + connect(ui->actionCopy_status, &QAction::triggered, activity_list_tab, &TabActivityList::on_buttonCopy_clicked); + connect(ui->actionFind, &QAction::triggered, activity_list_tab, &TabActivityList::on_buttonSearch_clicked); + connect(ui->actionOpen_URL, &QAction::triggered, activity_list_tab, &TabActivityList::actionOpen_URL_triggered); + connect(ui->menuView, &QMenu::aboutToHide, activity_list_tab, &TabActivityList::menuView_aboutToHide); + connect(this, &MainWindow::new_archive_opened, activity_list_tab, &TabActivityList::relist_statuses); + + // TODO: Add the "+" tab for opening new tabs +} + void MainWindow::act_command_line(CommandLineParsedOptions &options, QCommandLineParser &parser) { switch (options.result) { case CommandLineError: @@ -86,159 +106,6 @@ void MainWindow::on_actionAbout_triggered(bool checked) { QMessageBox::information(this, "title", "text"); } -void MainWindow::on_listWidget_itemActivated(QListWidgetItem *item) { - StatusListItem* status = dynamic_cast(item); - if (status != nullptr) { - status_info_widget->show_list_item(status); - } -} - -void MainWindow::on_actionRandom_status_triggered(bool checked) { - if (data_archive == nullptr) return; // No archive open, avoids crashing - - int index = QRandomGenerator::global()->bounded(ui->listWidget->count()); - QListWidgetItem* item = ui->listWidget->item(index); - on_listWidget_itemActivated(item); - ui->listWidget->setCurrentItem(item); -} - -void MainWindow::on_actionCopy_status_triggered(bool checked) { - if (data_archive == nullptr or ui->listWidget->selectedItems().isEmpty()) return; - - StatusListItem* item = dynamic_cast(ui->listWidget->selectedItems()[0]); - if (item != nullptr) { - QString status_text = data_archive->get_html_status_text(item->get_status_index()); - QMimeData* clipboard_data = new QMimeData; - clipboard_data->setHtml(status_text); - QGuiApplication::clipboard()->setMimeData(clipboard_data); - } -} - -void MainWindow::on_actionFind_triggered(bool checked) { - if (!find_dialog) { - find_dialog = new FindDialog(this); - find_dialog->set_qlist_widget(ui->listWidget); - connect(find_dialog, &FindDialog::item_selected, this, &MainWindow::select_list_item); - connect(this, &MainWindow::search_text_changed, find_dialog, &FindDialog::set_search_text); - connect(find_dialog, &FindDialog::search_text_changed, this, &MainWindow::set_search_text); - - if (not ui->textInputSearch->text().isEmpty()) - emit search_text_changed(ui->textInputSearch->text(), true); - } - - find_dialog->show(); - find_dialog->raise(); - find_dialog->activateWindow(); -} - -void MainWindow::select_list_item(QListWidgetItem* item) { - on_listWidget_itemActivated(item); -} - -void MainWindow::on_textInputSearch_textEdited(const QString &text) { - emit search_text_changed(text); -} - -void MainWindow::set_search_text(const QString &text) { - ui->textInputSearch->setText(text); -} - -void MainWindow::on_actionOpen_URL_triggered(bool checked) { - bool ok; - QString url = QInputDialog::getText(this, tr("Open status from URL"), tr("Status URL:"), QLineEdit::Normal, "https://…", &ok); - - // TODO: Move all of this to another thread - // TODO: Reuse the Instance object - // Really hacky code but works as a PoC and allows testing - if (ok and not url.isEmpty()) { - Instance* instance = Instance::create_instance(); - APPost* post = instance->get_post_from_url(url); - // Activity will be freed by StatusInfoWidget - APActivity* activity = new APActivity({.object = post}); - status_info_widget->do_process_activity(activity); - delete instance; instance = nullptr; - } else return; -} - -void MainWindow::relist_statuses() { - if (data_archive) { - ui->listWidget->clear(); - data_archive->update_status_list(view_filters, ui->listWidget); - view_filters_changed = false; - } -} - -// Function used to reset filters when we have detected that the "All toots" toggle has been toggled on or shouldn't be toggled anymore -void MainWindow::reset_view_filters() { - if (ui->actionAll_toots->isChecked()) - view_filters = {true, true, true, true, view_filters.includeReblogs, view_filters.onlyWithAttachment}; - else - view_filters = {false, false, false, false, view_filters.includeReblogs, view_filters.onlyWithAttachment}; -} - -void MainWindow::on_actionAll_toots_triggered(bool checked) { - ui->actionAll_toots->setChecked(true); - ui->actionPublic_toots->setChecked(false); - ui->actionUnlisted_toots->setChecked(false); - ui->actionPrivate_toots->setChecked(false); - ui->actionDirect_messages->setChecked(false); - ui->actionOnly_with_attachment->setChecked(false); - - reset_view_filters(); - relist_statuses(); -} - -void MainWindow::on_actionPublic_toots_triggered(bool checked) { - if (ui->actionAll_toots->isChecked()) { - ui->actionAll_toots->setChecked(false); - reset_view_filters(); - } - view_filters.includePublic = checked; - view_filters_changed = true; -} - -void MainWindow::on_actionUnlisted_toots_triggered(bool checked) { - if (ui->actionAll_toots->isChecked()) { - ui->actionAll_toots->setChecked(false); - reset_view_filters(); - } - view_filters.includeUnlisted = checked; - view_filters_changed = true; -} - -void MainWindow::on_actionPrivate_toots_triggered(bool checked) { - if (ui->actionAll_toots->isChecked()) { - ui->actionAll_toots->setChecked(false); - reset_view_filters(); - } - view_filters.includePrivate = checked; - view_filters_changed = true; -} - -void MainWindow::on_actionDirect_messages_triggered(bool checked) { - if (ui->actionAll_toots->isChecked()) { - ui->actionAll_toots->setChecked(false); - reset_view_filters(); - } - view_filters.includeDirect = checked; - view_filters_changed = true; -} - -void MainWindow::on_actionOnly_with_attachment_triggered(bool checked) { - view_filters.onlyWithAttachment = checked; - view_filters_changed = true; -} - -void MainWindow::on_actionReblogs_triggered(bool checked) { - view_filters.includeReblogs = checked; - view_filters_changed = true; -} - -void MainWindow::on_menuView_aboutToHide() { - if (view_filters_changed) - relist_statuses(); -} - void MainWindow::open_file(const QString &filename) { if (data_archive) { delete data_archive; @@ -247,6 +114,8 @@ void MainWindow::open_file(const QString &filename) { open_file_filename = filename; + // BUG: the overwritten cursor seems to get "lost" and revert in moc_mainwindow.cpp (after the end of the function we're in) when opening a file through a file dialog. + // The cursor behaves properly when opening a file throught the command line. QApplication::setOverrideCursor(Qt::WaitCursor); data_archive = Archive::create_archive(ArchiveType::MASTODON, filename); @@ -275,18 +144,13 @@ void MainWindow::finish_open_file(const Archive::InitError& parse_error) { break; } - ui->listWidget->clear(); - if (parse_error == Archive::NoError) { - data_archive->update_status_list(view_filters, ui->listWidget); + emit new_archive_opened(); } - - QApplication::restoreOverrideCursor(); + // The cursor is restored in TabActivityList::relist_statuses() } void MainWindow::archive_thread_watcher_done() { Archive::InitError result = archive_thread_watcher.result(); - - if (result) - finish_open_file(result); + finish_open_file(result); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 7e46cec..e77e2a9 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -4,14 +4,13 @@ #include #include #include +#include #include "archive/base_archive.h" #include "src/list_item.h" #include "src/settingsdialog.h" -#include "src/widgets/status_info.h" -#include "types.h" +#include "src/widgets/tab_activity_list.h" #include "command_line.h" -#include "finddialog.h" QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } @@ -28,11 +27,12 @@ public: void act_command_line(CommandLineParsedOptions &options, QCommandLineParser &parser); public slots: - void select_list_item(QListWidgetItem* item); - void set_search_text(const QString &text); + //void select_list_item(QListWidgetItem* item); + //void set_search_text(const QString &text); signals: void search_text_changed(const QString &text, bool search_immediately = false); + void new_archive_opened(); private slots: void on_actionOpen_triggered(bool checked); @@ -40,25 +40,8 @@ private slots: void on_actionQuit_triggered(bool checked); void on_actionAbout_triggered(bool checked); - void on_actionAll_toots_triggered(bool checked); - void on_actionPublic_toots_triggered(bool checked); - void on_actionUnlisted_toots_triggered(bool checked); - void on_actionPrivate_toots_triggered(bool checked); - void on_actionDirect_messages_triggered(bool checked); - void on_actionOnly_with_attachment_triggered(bool checked); - void on_actionReblogs_triggered(bool checked); - void on_actionRandom_status_triggered(bool checked); - void on_actionCopy_status_triggered(bool checked); - void on_actionFind_triggered(bool checked); - void on_actionOpen_URL_triggered(bool checked); - - void on_textInputSearch_textEdited(const QString &text); - - void on_menuView_aboutToHide(); - - void on_listWidget_itemActivated(QListWidgetItem *item); - private: + void create_initial_tabs(); void reset_view_filters(); void relist_statuses(); void open_file(const QString &filename); @@ -68,17 +51,13 @@ private: void finish_open_file(const Archive::InitError& parse_error); void settingsDialog_done(int result); - FindDialog* find_dialog = nullptr; SettingsDialog* settings_dialog = nullptr; - StatusInfoWidget* status_info_widget = nullptr; + TabActivityList* activity_list_tab = nullptr; QString open_file_filename; Ui::MainWindow *ui; - ViewStatusTypes view_filters; - bool view_filters_changed = false; - Archive *data_archive = nullptr; QFutureWatcher archive_thread_watcher; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 0ab8c02..40e1755 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -14,49 +14,14 @@ Actor Viewer - - - - - - - - - - Search - - - - - - - - - Qt::ScrollBarAlwaysOff - - - true + + + + + 0 - - - - - - Random status - - - - - - - Copy status text - - - - - @@ -255,10 +220,6 @@ - - listWidget - statusInfoText - diff --git a/src/widgets/tab_activity_list.cpp b/src/widgets/tab_activity_list.cpp new file mode 100644 index 0000000..03065eb --- /dev/null +++ b/src/widgets/tab_activity_list.cpp @@ -0,0 +1,182 @@ +#include "tab_activity_list.h" +#include "./ui_tab_activity_list.h" +#include "status_info.h" +#include "src/net/instance.h" + +#include +#include +#include +#include +#include +#include + +TabActivityList::TabActivityList(Archive** archive, QWidget* parent) + : data_archive(archive), QWidget(parent), ui(new Ui::TabActivityList) +{ + ui->setupUi(this); + // QGridLayouts have margins around each element, but here we already have margins outside of the element, so these ones are not welcome + ui->gridLayout->setContentsMargins(0, 0, 0, 0); + + status_info_widget = new StatusInfoWidget(this); + ui->gridLayout->addWidget(status_info_widget, 1, 1); +} + +TabActivityList::~TabActivityList() { + delete ui; +} + +void TabActivityList::on_listWidget_itemActivated(QListWidgetItem *item) { + StatusListItem* status = dynamic_cast(item); + if (status != nullptr) { + status_info_widget->show_list_item(status); + } +} + +void TabActivityList::on_buttonRandom_clicked() { + if ((*data_archive) == nullptr) return; // No archive open, avoids crashing + + int index = QRandomGenerator::global()->bounded(ui->listWidget->count()); + QListWidgetItem* item = ui->listWidget->item(index); + on_listWidget_itemActivated(item); + ui->listWidget->setCurrentItem(item); +} + +void TabActivityList::on_buttonCopy_clicked() { + if ((*data_archive) == nullptr or ui->listWidget->selectedItems().isEmpty()) return; + + StatusListItem* item = dynamic_cast(ui->listWidget->selectedItems()[0]); + if (item != nullptr) { + QString status_text = (*data_archive)->get_html_status_text(item->get_status_index()); + QMimeData* clipboard_data = new QMimeData; + clipboard_data->setHtml(status_text); + QGuiApplication::clipboard()->setMimeData(clipboard_data); + } +} + +void TabActivityList::on_buttonSearch_clicked() { + if (!find_dialog) { + find_dialog = new FindDialog(this); + find_dialog->set_qlist_widget(ui->listWidget); + connect(find_dialog, &FindDialog::item_selected, this, &TabActivityList::select_list_item); + connect(this, &TabActivityList::search_text_changed, find_dialog, &FindDialog::set_search_text); + connect(find_dialog, &FindDialog::search_text_changed, this, &TabActivityList::set_search_text); + + if (not ui->textInputSearch->text().isEmpty()) + emit search_text_changed(ui->textInputSearch->text(), true); + } + + find_dialog->show(); + find_dialog->raise(); + find_dialog->activateWindow(); +} + +void TabActivityList::select_list_item(QListWidgetItem* item) { + on_listWidget_itemActivated(item); +} + +void TabActivityList::on_textInputSearch_textEdited(const QString &text) { + emit search_text_changed(text); +} + +void TabActivityList::set_search_text(const QString &text) { + ui->textInputSearch->setText(text); +} + +void TabActivityList::actionOpen_URL_triggered(bool checked) { + bool ok; + QString url = QInputDialog::getText(this, tr("Open status from URL"), tr("Status URL:"), QLineEdit::Normal, "https://…", &ok); + + // TODO: Move all of this to another thread + // TODO: Reuse the Instance object + // Really hacky code but works as a PoC and allows testing + if (ok and not url.isEmpty()) { + Instance* instance = Instance::create_instance(); + APPost* post = instance->get_post_from_url(url); + // Activity will be freed by StatusInfoWidget + APActivity* activity = new APActivity({.object = post}); + status_info_widget->do_process_activity(activity); + delete instance; instance = nullptr; + } else return; +} + +void TabActivityList::relist_statuses() { + if (*data_archive) { + ui->listWidget->clear(); + (*data_archive)->update_status_list(view_filters, ui->listWidget); + view_filters_changed = false; + } + // Cursor overriden only when opening new archive + if (QApplication::overrideCursor()) + QApplication::restoreOverrideCursor(); +} + +// Function used to reset filters when we have detected that the "All toots" toggle has been toggled on or shouldn't be toggled anymore +void TabActivityList::reset_view_filters() { + if (view_actions.actionAll_toots->isChecked()) + view_filters = {true, true, true, true, view_filters.includeReblogs, view_filters.onlyWithAttachment}; + else + view_filters = {false, false, false, false, view_filters.includeReblogs, view_filters.onlyWithAttachment}; +} + +void TabActivityList::actionAll_toots_triggered(bool checked) { + view_actions.actionAll_toots->setChecked(true); + view_actions.actionPublic_toots->setChecked(false); + view_actions.actionUnlisted_toots->setChecked(false); + view_actions.actionPrivate_toots->setChecked(false); + view_actions.actionDirect_messages->setChecked(false); + view_actions.actionOnly_with_attachment->setChecked(false); + + reset_view_filters(); + relist_statuses(); +} + +void TabActivityList::actionPublic_toots_triggered(bool checked) { + if (view_actions.actionAll_toots->isChecked()) { + view_actions.actionAll_toots->setChecked(false); + reset_view_filters(); + } + view_filters.includePublic = checked; + view_filters_changed = true; +} + +void TabActivityList::actionUnlisted_toots_triggered(bool checked) { + if (view_actions.actionAll_toots->isChecked()) { + view_actions.actionAll_toots->setChecked(false); + reset_view_filters(); + } + view_filters.includeUnlisted = checked; + view_filters_changed = true; +} + +void TabActivityList::actionPrivate_toots_triggered(bool checked) { + if (view_actions.actionAll_toots->isChecked()) { + view_actions.actionAll_toots->setChecked(false); + reset_view_filters(); + } + view_filters.includePrivate = checked; + view_filters_changed = true; +} + +void TabActivityList::actionDirect_messages_triggered(bool checked) { + if (view_actions.actionAll_toots->isChecked()) { + view_actions.actionAll_toots->setChecked(false); + reset_view_filters(); + } + view_filters.includeDirect = checked; + view_filters_changed = true; +} + +void TabActivityList::actionOnly_with_attachment_triggered(bool checked) { + view_filters.onlyWithAttachment = checked; + view_filters_changed = true; +} + +void TabActivityList::actionReblogs_triggered(bool checked) { + view_filters.includeReblogs = checked; + view_filters_changed = true; +} + +void TabActivityList::menuView_aboutToHide() { + if (view_filters_changed) + relist_statuses(); +} diff --git a/src/widgets/tab_activity_list.h b/src/widgets/tab_activity_list.h new file mode 100644 index 0000000..ae6d426 --- /dev/null +++ b/src/widgets/tab_activity_list.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include "src/widgets/status_info.h" +#include "src/finddialog.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class TabActivityList; } +QT_END_NAMESPACE + +class TabActivityList : public QWidget { + Q_OBJECT + +public: + struct ViewActions { + QAction* actionAll_toots = nullptr; + QAction* actionPublic_toots = nullptr; + QAction* actionUnlisted_toots = nullptr; + QAction* actionPrivate_toots = nullptr; + QAction* actionDirect_messages = nullptr; + QAction* actionOnly_with_attachment = nullptr; + } view_actions; + + + TabActivityList(Archive** archive, QWidget *parent = nullptr); + ~TabActivityList(); + +public slots: + void actionAll_toots_triggered(bool checked); + void actionPublic_toots_triggered(bool checked); + void actionUnlisted_toots_triggered(bool checked); + void actionPrivate_toots_triggered(bool checked); + void actionDirect_messages_triggered(bool checked); + void actionOnly_with_attachment_triggered(bool checked); + void actionReblogs_triggered(bool checked); + + void on_buttonRandom_clicked(); + void on_buttonCopy_clicked(); + void on_buttonSearch_clicked(); + + void actionOpen_URL_triggered(bool checked); + void menuView_aboutToHide(); + + void relist_statuses(); + + void select_list_item(QListWidgetItem* item); + void set_search_text(const QString &text); + +signals: + void search_text_changed(const QString &text, bool search_immediately = false); + +private slots: + void on_textInputSearch_textEdited(const QString &text); + void on_listWidget_itemActivated(QListWidgetItem *item); + +private: + void reset_view_filters(); + + StatusInfoWidget* status_info_widget = nullptr; + + Ui::TabActivityList* ui; + + // Pointer to MainWindow's data archive pointer + Archive **data_archive = nullptr; + FindDialog* find_dialog = nullptr; + + ViewStatusTypes view_filters; + bool view_filters_changed = false; +}; diff --git a/src/widgets/tab_activity_list.ui b/src/widgets/tab_activity_list.ui new file mode 100644 index 0000000..989aa12 --- /dev/null +++ b/src/widgets/tab_activity_list.ui @@ -0,0 +1,63 @@ + + + TabActivityList + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + + + + + + Search + + + + + + + + + + + Random status + + + + + + + Copy status text + + + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + + + + + -- cgit v1.2.3-54-g00ecf