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