diff --git a/DobieStation/DobieQt.vcxproj b/DobieStation/DobieQt.vcxproj index bb311bc83..87844e64d 100644 --- a/DobieStation/DobieQt.vcxproj +++ b/DobieStation/DobieQt.vcxproj @@ -85,10 +85,13 @@ - + + + + @@ -103,17 +106,23 @@ - + + + + - - - - - - + + + + + + + + + diff --git a/DobieStation/DobieQt.vcxproj.filters b/DobieStation/DobieQt.vcxproj.filters index e073d2fe8..0c613b5ab 100644 --- a/DobieStation/DobieQt.vcxproj.filters +++ b/DobieStation/DobieQt.vcxproj.filters @@ -19,9 +19,6 @@ Source - - Source - Source @@ -40,25 +37,46 @@ Source + + Source + + + Source + + + Source + + + Source + - + + Moc + + + Moc + + Moc - + Moc - + Moc - + Moc - + Moc - + + Moc + + Moc @@ -70,7 +88,7 @@ - + Headers @@ -85,7 +103,16 @@ Headers - + + Headers + + + Headers + + + Headers + + Headers diff --git a/DobieStation/application/application.pro b/DobieStation/application/application.pro index 084c84054..a8c9a4c5c 100644 --- a/DobieStation/application/application.pro +++ b/DobieStation/application/application.pro @@ -92,7 +92,10 @@ SOURCES += ../../src/qt/main.cpp \ ../../src/qt/renderwidget.cpp \ ../../src/qt/settingswindow.cpp \ ../../src/qt/bios.cpp \ - ../../src/qt/gamelistwidget.cpp \ + ../../src/qt/gamelist/gamelistmodel.cpp \ + ../../src/qt/gamelist/gamelistproxy.cpp \ + ../../src/qt/gamelist/gamelistwatcher.cpp \ + ../../src/qt/gamelist/gamelistwidget.cpp \ ../../src/core/ee/ee_jittrans.cpp \ ../../src/core/ee/ee_jit.cpp \ ../../src/core/ee/ee_jit64.cpp \ @@ -171,7 +174,10 @@ HEADERS += \ ../../src/qt/renderwidget.hpp \ ../../src/qt/settingswindow.hpp \ ../../src/qt/bios.hpp \ - ../../src/qt/gamelistwidget.hpp \ + ../../src/qt/gamelist/gamelistmodel.hpp \ + ../../src/qt/gamelist/gamelistproxy.hpp \ + ../../src/qt/gamelist/gamelistwatcher.hpp \ + ../../src/qt/gamelist/gamelistwidget.hpp \ ../../src/core/ee/ee_jittrans.hpp \ ../../src/core/ee/ee_jit.hpp \ ../../src/core/ee/ee_jit64.hpp \ diff --git a/DobieStation/common.props b/DobieStation/common.props index 43d54a60a..4cdbc1692 100644 --- a/DobieStation/common.props +++ b/DobieStation/common.props @@ -29,7 +29,6 @@ 4577;4244;4267;%(DisableSpecificWarnings) - $(UIDir);%(AdditionalIncludeDirectories) $(ExtDir)\libdeflate;$(ExtDir)\libdeflate\common;%(AdditionalIncludeDirectories) diff --git a/DobieStation/qt.props b/DobieStation/qt.props index 24b137a9c..8d2f7addb 100644 --- a/DobieStation/qt.props +++ b/DobieStation/qt.props @@ -13,7 +13,8 @@ $(QTDIR)lib\ $(QTDIR)bin\ $(QTDIR)plugins\ - $(QtToolOutDir)moc_ + $(QtToolOutDir) + moc_ d $(QtDebugSuffix) QtPlugins @@ -32,6 +33,8 @@ QT_CORE_LIB;%(PreprocessorDefinitions) + $(UIDir);%(AdditionalIncludeDirectories) + $(UIDir)gamelist;%(AdditionalIncludeDirectories) $(QtToolOutDir);%(AdditionalIncludeDirectories) $(QtIncludeDir);%(AdditionalIncludeDirectories) $(QtIncludeDir)QtCore;%(AdditionalIncludeDirectories) @@ -76,6 +79,7 @@ + diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index c34015af7..758f97662 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -18,19 +18,25 @@ set(SOURCES emuwindow.cpp settingswindow.cpp renderwidget.cpp - gamelistwidget.cpp main.cpp settings.cpp - bios.cpp) + bios.cpp + gamelist/gamelistmodel.cpp + gamelist/gamelistproxy.cpp + gamelist/gamelistwatcher.cpp + gamelist/gamelistwidget.cpp) set(HEADERS emuthread.hpp emuwindow.hpp settingswindow.hpp renderwidget.hpp - gamelistwidget.hpp settings.hpp - bios.hpp) + bios.hpp + gamelist/gamelistmodel.hpp + gamelist/gamelistproxy.hpp + gamelist/gamelistwatcher.hpp + gamelist/gamelistwidget.hpp) add_executable(${TARGET} ${SOURCES} ${HEADERS}) set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "DobieStation") # Output as "DobieStation" instead of "DobieQt" diff --git a/src/qt/emuwindow.cpp b/src/qt/emuwindow.cpp index 1569e8a27..5c6be04c2 100644 --- a/src/qt/emuwindow.cpp +++ b/src/qt/emuwindow.cpp @@ -14,9 +14,10 @@ #include "emuwindow.hpp" #include "settingswindow.hpp" #include "renderwidget.hpp" -#include "gamelistwidget.hpp" #include "bios.hpp" +#include "gamelist/gamelistwidget.hpp" + #include "arg.h" using namespace std; diff --git a/src/qt/gamelist/gamelistmodel.cpp b/src/qt/gamelist/gamelistmodel.cpp new file mode 100644 index 000000000..2a042d93f --- /dev/null +++ b/src/qt/gamelist/gamelistmodel.cpp @@ -0,0 +1,162 @@ +#include +#include +#include + +// For old Qt workaround +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) +#include +#endif + +#include "../settings.hpp" +#include "gamelistmodel.hpp" + +static QMap s_file_type_desc{ + {"elf", "PS2 Executable"}, + {"iso", "ISO Image"}, + {"cso", "Compressed ISO"}, + {"gsd", "GSDump"}, + {"bin", "BIN/CUE"} +}; + +GameListModel::GameListModel(QObject* parent) + : QAbstractTableModel(parent) +{ + connect(&watcher, &GameListWatcher::game_loaded, this, &GameListModel::add_game); + connect(&watcher, &GameListWatcher::game_removed, this, &GameListModel::remove_game); + connect(&watcher, &GameListWatcher::directory_processed, [this]() { + emit directory_processed(); + }); + + connect(&Settings::instance(), &Settings::rom_directory_committed, + &watcher, &GameListWatcher::add_directory); + connect(&Settings::instance(), &Settings::rom_directory_uncommitted, + &watcher, &GameListWatcher::remove_directory); + + for (const auto& path : Settings::instance().rom_directories) + watcher.add_directory(path); +} + +QVariant GameListModel::data( + const QModelIndex& index, int role +) const +{ + // might be cool in the future + // but for now let's just focus + // on display role + if (!index.isValid()) + return QVariant(); + + const auto& game = m_games.at(index.row()); + + switch (index.column()) + { + case COLUMN_NAME: + if(role == Qt::DisplayRole || role == Qt::InitialSortOrderRole) + return game.name; + break; + case COLUMN_TYPE: + if (role == Qt::DisplayRole) + return s_file_type_desc[game.type]; + + // sort by the actual type and not + // the description + if (role == Qt::InitialSortOrderRole) + return game.type; + break; + case COLUMN_SIZE: + if(role == Qt::DisplayRole) + return get_formatted_data_size(game.size); + + // sort by the actual size and not + // the human readable string + if (role == Qt::InitialSortOrderRole) + return game.size; + break; + } + + return QVariant(); +} + +QVariant GameListModel::headerData( + int section, Qt::Orientation orientation, int role +) const +{ + if (orientation == Qt::Vertical || role != Qt::DisplayRole) + return QVariant(); + + switch (section) + { + case COLUMN_NAME: + return tr("Name"); + case COLUMN_TYPE: + return tr("Type"); + case COLUMN_SIZE: + return tr("Size"); + } + + return QVariant(); +} + +int GameListModel::rowCount(const QModelIndex& index) const +{ + if (index.isValid()) + return 0; + + return m_games.size(); +} + +int GameListModel::columnCount(const QModelIndex& index) const +{ + return COLUMN_MAX; +} + +GameInfo GameListModel::get_game_info(int index) +{ + return m_games[index]; +} + +GameListWatcher* GameListModel::get_watcher() +{ + return &watcher; +} + +void GameListModel::add_game(const GameInfo game) +{ + beginInsertRows(QModelIndex(), m_games.size(), m_games.size()); + m_games.push_back(game); + endInsertRows(); +} + +void GameListModel::remove_game(const GameInfo game) +{ + auto index = m_games.indexOf(game); + + beginRemoveRows(QModelIndex(), index, index); + m_games.removeAt(index); + endRemoveRows(); +} + +QString GameListModel::get_formatted_data_size(uint64_t size) const +{ +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + // work around until ubuntu gets a new qt + // don't rely on this function for translations + const char* const quantifiers[] = { + "B", + "KiB", + "MiB", + "GiB", + "TiB", + "PiB", + "EiB" + }; + const int unit = std::log2(std::max(size, 1)) / 10; + const double unit_size = size / std::pow(2, unit * 10); + + return QString("%1 %2") + .arg(unit_size, 4, 'f', 2, '0') + .arg(quantifiers[unit]); +#else + return QLocale().formattedDataSize(size); +#endif +} diff --git a/src/qt/gamelist/gamelistmodel.hpp b/src/qt/gamelist/gamelistmodel.hpp new file mode 100644 index 000000000..1735ee917 --- /dev/null +++ b/src/qt/gamelist/gamelistmodel.hpp @@ -0,0 +1,47 @@ +#ifndef GAMELISTMODEL_HPP +#define GAMELISTMODEL_HPP + +#include +#include "gamelistwatcher.hpp" + +class GameListModel final : public QAbstractTableModel +{ + Q_OBJECT + + private: + QList m_games; + GameListWatcher watcher; + + public: + enum + { + COLUMN_NAME, + COLUMN_SIZE, + COLUMN_TYPE, + COLUMN_MAX + }; + + explicit GameListModel(QObject* parent = nullptr); + + QVariant data(const QModelIndex& index, + int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation, + int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex& index) const override; + int columnCount(const QModelIndex& index) const override; + + GameInfo get_game_info(int index); + GameListWatcher* get_watcher(); + + public slots: + void add_game(const GameInfo game); + void remove_game(const GameInfo game); + + signals: + void directory_processed(); + + private: + QString get_formatted_data_size(uint64_t size) const; +}; + +#endif diff --git a/src/qt/gamelist/gamelistproxy.cpp b/src/qt/gamelist/gamelistproxy.cpp new file mode 100644 index 000000000..d5f0dc4aa --- /dev/null +++ b/src/qt/gamelist/gamelistproxy.cpp @@ -0,0 +1,31 @@ +#include "gamelistproxy.hpp" +#include "gamelistmodel.hpp" + +GameListProxy::GameListProxy(QObject* parent) + : QSortFilterProxyModel(parent) +{ + setDynamicSortFilter(true); + setSortRole(Qt::InitialSortOrderRole); + setSortCaseSensitivity(Qt::CaseInsensitive); + setFilterCaseSensitivity(Qt::CaseInsensitive); +} + +bool GameListProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const +{ + if (left.data(Qt::InitialSortOrderRole) != right.data(Qt::InitialSortOrderRole)) + return QSortFilterProxyModel::lessThan(right, left); + + // if we are sorting by some other field than name + // and the field is otherwise equal, then sort by name + // (IE if we are sorting by type and both are ISO) + const auto& right_index = sourceModel()->index(right.row(), GameListModel::COLUMN_NAME); + const auto& left_index = sourceModel()->index(left.row(), GameListModel::COLUMN_NAME); + + const auto& right_title = right_index.data().toString(); + const auto& left_title = left_index.data().toString(); + + if (sortOrder() == Qt::AscendingOrder) + return left_title < right_title; + + return right_title < left_title; +} diff --git a/src/qt/gamelist/gamelistproxy.hpp b/src/qt/gamelist/gamelistproxy.hpp new file mode 100644 index 000000000..97e506e31 --- /dev/null +++ b/src/qt/gamelist/gamelistproxy.hpp @@ -0,0 +1,16 @@ +#ifndef GAMELISTPROXY_HPP +#define GAMELISTPROXY_HPP + +#include + +class GameListProxy final : public QSortFilterProxyModel +{ + Q_OBJECT + + public: + explicit GameListProxy(QObject* parent = nullptr); + + protected: + bool lessThan(const QModelIndex& left, const QModelIndex& right) const override; +}; +#endif \ No newline at end of file diff --git a/src/qt/gamelist/gamelistwatcher.cpp b/src/qt/gamelist/gamelistwatcher.cpp new file mode 100644 index 000000000..c01a562a1 --- /dev/null +++ b/src/qt/gamelist/gamelistwatcher.cpp @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include + +#include "gamelistwatcher.hpp" + +void IOTask::add_directory(const QString dir) +{ + const QStringList file_types({ + "*.iso", + "*.cso", + "*.elf", + "*.gsd", + "*.bin" + }); + + QDirIterator it(dir, file_types, + QDir::Files, QDirIterator::Subdirectories + ); + + QList list; + while (it.hasNext()) + { + it.next(); + + auto file_info = QFileInfo(it.filePath()); + + GameInfo info = {}; + info.path = it.filePath(); + info.name = file_info.baseName(); + info.size = file_info.size(); + info.type = file_info.suffix().toLower(); + + list.append(info); + emit game_found(info); + } + + m_path_map.insert(dir, list); + emit finished(); +} + +void IOTask::remove_directory(const QString dir) +{ + auto games = m_path_map.take(dir); + + for (const auto game : games) + emit game_removed(game); + + emit finished(); +} + +GameListWatcher::GameListWatcher(QObject* parent) +{ + // needed to communicate this type between threads + qRegisterMetaType("GameInfo"); + + m_task.moveToThread(&m_io_thread); + + connect(&m_io_thread, &QThread::started, []() { + qDebug() << "[gamelist] IO thread started"; + }); + connect(&m_io_thread, &QThread::finished, []() { + qDebug() << "[gamelist] IO thread shutdown"; + }); + + connect(this, &GameListWatcher::directory_added, &m_task, &IOTask::add_directory); + connect(this, &GameListWatcher::directory_removed, &m_task, &IOTask::remove_directory); + + connect(&m_task, &IOTask::game_found, this, &GameListWatcher::game_loaded); + connect(&m_task, &IOTask::game_removed, this, &GameListWatcher::game_removed); + + connect(&m_task, &IOTask::finished, [this]() { + qDebug() << "[gamelist] IO task finished"; + emit directory_processed(); + }); +} + +void GameListWatcher::start() +{ + m_io_thread.start(); +} + +void GameListWatcher::add_directory(const QString& dir) +{ + qDebug() << "[gamelist] Adding path: " << dir; + emit directory_added(dir); +} + +void GameListWatcher::remove_directory(const QString& dir) +{ + qDebug() << "[gamelist] Removing path: " << dir; + emit directory_removed(dir); +} + +GameListWatcher::~GameListWatcher() +{ + m_io_thread.quit(); + m_io_thread.wait(); +} diff --git a/src/qt/gamelist/gamelistwatcher.hpp b/src/qt/gamelist/gamelistwatcher.hpp new file mode 100644 index 000000000..916068e1a --- /dev/null +++ b/src/qt/gamelist/gamelistwatcher.hpp @@ -0,0 +1,63 @@ +#ifndef GAMELISTWATCHER_HPP +#define GAMELISTWATCHER_HPP +#include +#include +#include + +struct GameInfo +{ + QString path; + QString name; + QString type; + qint64 size; + + bool operator==(const GameInfo& other) const + { + return path == other.path; + } +}; + + +// Everything in IOTask executes on another thread +// with another event loop +class IOTask : public QObject +{ + Q_OBJECT + + private: + QMap> m_path_map; + public slots: + void add_directory(const QString dir); + void remove_directory(const QString dir); + signals: + void game_found(const GameInfo game); + void game_removed(const GameInfo game); + void finished(); +}; + +class GameListWatcher : public QObject +{ + Q_OBJECT + + private: + QThread m_io_thread; + IOTask m_task; + + public: + explicit GameListWatcher(QObject* parent = nullptr); + ~GameListWatcher(); + + void start(); + void add_directory(const QString& dir); + void remove_directory(const QString& dir); + + signals: + void game_loaded(const GameInfo info); + void game_updated(const GameInfo info); + void game_removed(const GameInfo info); + void directory_added(const QString dir); + void directory_removed(const QString dir); + void directory_processed(); +}; + +#endif \ No newline at end of file diff --git a/src/qt/gamelist/gamelistwidget.cpp b/src/qt/gamelist/gamelistwidget.cpp new file mode 100644 index 000000000..c55d102ad --- /dev/null +++ b/src/qt/gamelist/gamelistwidget.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../settings.hpp" +#include "gamelistwidget.hpp" + +#include +#include + +GameListWidget::GameListWidget(QWidget* parent) + : QStackedWidget(parent) +{ + m_table_view = new QTableView(this); + m_model = new GameListModel(this); + m_proxy = new GameListProxy(this); + + m_proxy->setSourceModel(m_model); + m_table_view->setModel(m_proxy); + + m_table_view->setShowGrid(false); + m_table_view->setFrameStyle(QFrame::NoFrame); + m_table_view->setAlternatingRowColors(true); + m_table_view->setSortingEnabled(true); + m_table_view->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table_view->verticalHeader()->setDefaultSectionSize(40); + + auto header = m_table_view->horizontalHeader(); + header->setMinimumSectionSize(40); + header->setSectionResizeMode(GameListModel::COLUMN_NAME, QHeaderView::Stretch); + + auto default_label = new QLabel(this); + default_label->setAlignment(Qt::AlignHCenter); + default_label->setText(tr( + "No roms found.\n" + "Add a new rom directory to see your roms listed here." + )); + + auto default_button = new QPushButton("&Open Settings", this); + + auto default_layout = new QVBoxLayout; + default_layout->addWidget(default_label); + default_layout->addWidget(default_button); + default_layout->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); + + auto gamelist_layout = new QVBoxLayout; + auto search_bar = new QLineEdit(this); + gamelist_layout->addWidget(search_bar); + gamelist_layout->addWidget(m_table_view); + gamelist_layout->setContentsMargins(0, 0, 0, 0); + + auto default_widget = new QWidget(this); + default_widget->setLayout(default_layout); + + auto gamelist_widget = new QWidget(this); + gamelist_widget->setLayout(gamelist_layout); + + connect(default_button, &QPushButton::clicked, [=]() { + emit settings_requested(); + }); + + connect(m_model->get_watcher(), &GameListWatcher::directory_processed, + this, &GameListWidget::update_view); + + connect(m_table_view, &QTableView::doubleClicked, [=](const QModelIndex& index) { + auto proxy_index = m_proxy->mapToSource(index); + + GameInfo info = m_model->get_game_info(proxy_index.row()); + emit game_double_clicked(info.path); + }); + + connect(search_bar, &QLineEdit::textChanged, + m_proxy, &QSortFilterProxyModel::setFilterFixedString); + + addWidget(default_widget); + addWidget(gamelist_widget); + + m_model->get_watcher()->start(); +} + +void GameListWidget::update_view() +{ + if(m_table_view->model()->rowCount() || currentIndex() != VIEW::GAMELIST) + setCurrentIndex(VIEW::GAMELIST); + else if(!m_table_view->model()->rowCount()) + setCurrentIndex(VIEW::DEFAULT); +} diff --git a/src/qt/gamelist/gamelistwidget.hpp b/src/qt/gamelist/gamelistwidget.hpp new file mode 100644 index 000000000..4f5dd03bf --- /dev/null +++ b/src/qt/gamelist/gamelistwidget.hpp @@ -0,0 +1,31 @@ +#ifndef GAMELISTWIDGET_HPP +#define GAMELISTWIDGET_HPP + +#include +#include +#include + +#include "gamelistmodel.hpp" +#include "gamelistproxy.hpp" + +class GameListWidget : public QStackedWidget +{ + Q_OBJECT + private: + GameListModel* m_model; + GameListProxy* m_proxy; + QTableView* m_table_view; + public: + enum VIEW + { + DEFAULT, + GAMELIST + }; + GameListWidget(QWidget* parent = nullptr); + public slots: + void update_view(); + signals: + void game_double_clicked(QString path); + void settings_requested(); +}; +#endif diff --git a/src/qt/gamelistwidget.cpp b/src/qt/gamelistwidget.cpp deleted file mode 100644 index ee3e2dba8..000000000 --- a/src/qt/gamelistwidget.cpp +++ /dev/null @@ -1,239 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "gamelistwidget.hpp" -#include "settings.hpp" - -#include -#include - -GameListModel::GameListModel(QObject* parent) - : QAbstractTableModel(parent) -{ - for (auto& path : Settings::instance().rom_directories) - { - games.append(get_directory_entries(path)); - } - - sort_games(); - - connect(&Settings::instance(), &Settings::rom_directory_committed, - this, &GameListModel::add_path - ); - - connect(&Settings::instance(), &Settings::rom_directory_uncommitted, - this, &GameListModel::remove_path - ); -} - -QVariant GameListModel::data( - const QModelIndex& index, int role -) const -{ - // might be cool in the future - // but for now let's just focus - // on display role - if (role != Qt::DisplayRole) - return QVariant(); - - auto file = games.at(index.row()); - QFileInfo file_info(file); - - switch (index.column()) - { - case COLUMN_NAME: - return file_info.fileName(); - case COLUMN_SIZE: - return get_formatted_data_size(file_info.size()); - } - - return QVariant(); -} - -QVariant GameListModel::headerData( - int section, Qt::Orientation orientation, int role -) const -{ - switch (section) - { - case COLUMN_NAME: - return tr("Name"); - case COLUMN_SIZE: - return tr("Size"); - } - - return QVariant(); -} - -int GameListModel::rowCount(const QModelIndex& index) const -{ - return games.size(); -} - -int GameListModel::columnCount(const QModelIndex& index) const -{ - return 2; -} - -QStringList GameListModel::get_directory_entries(QString path) -{ - const QStringList file_types({ - "*.iso", - "*.cso", - "*.elf", - "*.gsd", - "*.bin" - }); - - QDirIterator it(path, file_types, - QDir::Files, QDirIterator::Subdirectories - ); - - QStringList list; - while (it.hasNext()) - { - it.next(); - list.append(it.filePath()); - } - - return list; -} - -QString GameListModel::get_formatted_data_size(uint64_t size) const -{ -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - // work around until ubuntu gets a new qt - // don't rely on this function for translations - const char* const quantifiers[] = { - "B", - "KiB", - "MiB", - "GiB", - "TiB", - "PiB", - "EiB" - }; - const int unit = std::log2(std::max(size, 1)) / 10; - const double unit_size = size / std::pow(2, unit * 10); - - return QString("%1 %2") - .arg(unit_size, 4, 'f', 2, '0') - .arg(quantifiers[unit]); -#else - return QLocale().formattedDataSize(size); -#endif -} - -void GameListModel::sort_games() -{ - std::sort(games.begin(), games.end(), [&](const QString& file1, const QString& file2) { - auto file_name1 = QFileInfo(file1).fileName(); - auto file_name2 = QFileInfo(file2).fileName(); - - return file_name1.compare(file_name2, Qt::CaseInsensitive) < 0; - }); -} - -void GameListModel::add_path(const QString& path) -{ - QStringList new_games = get_directory_entries(path); - - beginInsertRows(QModelIndex(), 0, new_games.size() - 1); - games.append(new_games); - - sort_games(); - - games.removeDuplicates(); - endInsertRows(); -} - -void GameListModel::remove_path(const QString& path) -{ - QStringList remove_games = get_directory_entries(path); - - for (auto& game : remove_games) - { - int index = games.indexOf(game); - if (index == -1) - continue; - - beginRemoveRows(QModelIndex(), index, index); - games.removeAll(game); - endRemoveRows(); - } -} - -GameListWidget::GameListWidget(QWidget* parent) - : QStackedWidget(parent) -{ - auto game_list_widget = new QTableView(this); - auto game_list_model = new GameListModel(this); - - game_list_widget->setModel(game_list_model); - game_list_widget->setShowGrid(false); - game_list_widget->setFrameStyle(QFrame::NoFrame); - game_list_widget->setAlternatingRowColors(true); - game_list_widget->setSelectionMode(QAbstractItemView::ExtendedSelection); - game_list_widget->setSelectionBehavior(QAbstractItemView::SelectRows); - game_list_widget->verticalHeader()->setDefaultSectionSize(40); - - auto header = game_list_widget->horizontalHeader(); - header->setMinimumSectionSize(40); - header->setSectionResizeMode(GameListModel::COLUMN_NAME, QHeaderView::Stretch); - - connect(game_list_widget, &QTableView::doubleClicked, [=](const QModelIndex& index) { - QString path = game_list_model->games.at(index.row()); - emit game_double_clicked(path); - }); - - auto default_label = new QLabel(this); - default_label->setAlignment(Qt::AlignHCenter); - default_label->setText(tr( - "No roms found.\n" - "Add a new rom directory to see your roms listed here." - )); - - auto default_button = new QPushButton("&Open Settings", this); - connect(default_button, &QPushButton::clicked, [=]() { - emit settings_requested(); - }); - - auto layout = new QVBoxLayout; - layout->addWidget(default_label); - layout->addWidget(default_button); - layout->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); - - auto default_widget = new QWidget(this); - default_widget->setLayout(layout); - - addWidget(default_widget); - addWidget(game_list_widget); - - if (game_list_widget->model()->rowCount()) - show_gamelist_view(); - - connect(&Settings::instance(), &Settings::rom_directory_committed, - this, &GameListWidget::show_gamelist_view - ); - - connect(&Settings::instance(), &Settings::rom_directory_uncommitted, [=]() { - if (!game_list_widget->model()->rowCount()) - show_default_view(); - }); -} - -void GameListWidget::show_default_view() -{ - setCurrentIndex(VIEW::DEFAULT); -} - -void GameListWidget::show_gamelist_view() -{ - setCurrentIndex(VIEW::GAMELIST); -} \ No newline at end of file diff --git a/src/qt/gamelistwidget.hpp b/src/qt/gamelistwidget.hpp deleted file mode 100644 index cd1da7d9b..000000000 --- a/src/qt/gamelistwidget.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef GAMELISTWIDGET_HPP -#define GAMELISTWIDGET_HPP - -#include -#include -#include - -class GameListModel final : public QAbstractTableModel -{ - Q_OBJECT - public: - enum - { - COLUMN_NAME, - COLUMN_SIZE - }; - - QStringList games; - - explicit GameListModel(QObject* parent = nullptr); - - QVariant data(const QModelIndex& index, - int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation, - int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex& index) const override; - int columnCount(const QModelIndex& index) const override; - - void add_path(const QString& path); - void remove_path(const QString& path); - private: - void sort_games(); - QStringList get_directory_entries(QString path); - QString get_formatted_data_size(uint64_t size) const; -}; - -class GameListWidget : public QStackedWidget -{ - Q_OBJECT - public: - enum VIEW - { - DEFAULT, - GAMELIST - }; - GameListWidget(QWidget* parent = nullptr); - - void show_default_view(); - void show_gamelist_view(); - signals: - void game_double_clicked(QString path); - void settings_requested(); -}; -#endif \ No newline at end of file