From 949f25175b90bca6575f5e731e9f0f56f49f976c Mon Sep 17 00:00:00 2001 From: spxtr Date: Fri, 27 Nov 2015 00:33:07 -0800 Subject: [PATCH] DolphinQt2 --- CMakeLists.txt | 3 +- Source/Core/CMakeLists.txt | 3 + Source/Core/DolphinQt2/CMakeLists.txt | 25 ++ Source/Core/DolphinQt2/GameList/GameFile.cpp | 202 +++++++++ Source/Core/DolphinQt2/GameList/GameFile.h | 101 +++++ Source/Core/DolphinQt2/GameList/GameList.cpp | 88 ++++ Source/Core/DolphinQt2/GameList/GameList.h | 41 ++ .../DolphinQt2/GameList/GameListModel.cpp | 132 ++++++ .../Core/DolphinQt2/GameList/GameListModel.h | 55 +++ .../Core/DolphinQt2/GameList/GameTracker.cpp | 84 ++++ Source/Core/DolphinQt2/GameList/GameTracker.h | 66 +++ Source/Core/DolphinQt2/Host.cpp | 96 +++++ Source/Core/DolphinQt2/Host.h | 45 ++ Source/Core/DolphinQt2/Main.cpp | 33 ++ Source/Core/DolphinQt2/MainWindow.cpp | 404 ++++++++++++++++++ Source/Core/DolphinQt2/MainWindow.h | 61 +++ Source/Core/DolphinQt2/RenderWidget.cpp | 51 +++ Source/Core/DolphinQt2/RenderWidget.h | 25 ++ Source/Core/DolphinQt2/Resources.cpp | 83 ++++ Source/Core/DolphinQt2/Resources.h | 36 ++ 20 files changed, 1633 insertions(+), 1 deletion(-) create mode 100644 Source/Core/DolphinQt2/CMakeLists.txt create mode 100644 Source/Core/DolphinQt2/GameList/GameFile.cpp create mode 100644 Source/Core/DolphinQt2/GameList/GameFile.h create mode 100644 Source/Core/DolphinQt2/GameList/GameList.cpp create mode 100644 Source/Core/DolphinQt2/GameList/GameList.h create mode 100644 Source/Core/DolphinQt2/GameList/GameListModel.cpp create mode 100644 Source/Core/DolphinQt2/GameList/GameListModel.h create mode 100644 Source/Core/DolphinQt2/GameList/GameTracker.cpp create mode 100644 Source/Core/DolphinQt2/GameList/GameTracker.h create mode 100644 Source/Core/DolphinQt2/Host.cpp create mode 100644 Source/Core/DolphinQt2/Host.h create mode 100644 Source/Core/DolphinQt2/Main.cpp create mode 100644 Source/Core/DolphinQt2/MainWindow.cpp create mode 100644 Source/Core/DolphinQt2/MainWindow.h create mode 100644 Source/Core/DolphinQt2/RenderWidget.cpp create mode 100644 Source/Core/DolphinQt2/RenderWidget.h create mode 100644 Source/Core/DolphinQt2/Resources.cpp create mode 100644 Source/Core/DolphinQt2/Resources.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fed4b68a8..f51696e3c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ option(USE_SHARED_ENET "Use shared libenet if found rather than Dolphin's soon-t option(USE_UPNP "Enables UPnP port mapping support" ON) option(DISABLE_WX "Disable wxWidgets (use Qt or CLI interface)" OFF) option(ENABLE_QT "Enable Qt (use the experimental Qt interface)" OFF) +option(ENABLE_QT2 "Enable Qt2 (use the other experimental Qt interface)" OFF) option(ENABLE_PCH "Use PCH to speed up compilation" ON) option(ENABLE_LTO "Enables Link Time Optimization" OFF) option(ENABLE_GENERIC "Enables generic build that should run on any little-endian host" OFF) @@ -751,7 +752,7 @@ else() mark_as_advanced(ICONV_INCLUDE_DIR ICONV_LIBRARIES) endif() -if(ENABLE_QT) +if(ENABLE_QT OR ENABLE_QT2) find_package(Qt5Widgets REQUIRED) message("Found Qt version ${Qt5Core_VERSION}, enabling the Qt backend") endif() diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt index 710a439948..8da95c59a9 100644 --- a/Source/Core/CMakeLists.txt +++ b/Source/Core/CMakeLists.txt @@ -11,3 +11,6 @@ add_subdirectory(VideoBackends) if(ENABLE_QT) add_subdirectory(DolphinQt) endif() +if(ENABLE_QT2) + add_subdirectory(DolphinQt2) +endif() diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt new file mode 100644 index 0000000000..8a744f8017 --- /dev/null +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -0,0 +1,25 @@ +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +add_definitions(-DQT_USE_QSTRINGBUILDER -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII) +set(CMAKE_AUTOMOC ON) + +set(SRCS + Main.cpp + MainWindow.cpp + Host.cpp + RenderWidget.cpp + Resources.cpp + GameList/GameFile.cpp + GameList/GameList.cpp + GameList/GameTracker.cpp + GameList/GameListModel.cpp + ) + +list(APPEND LIBS core uicommon) + +set(DOLPHINQT2_BINARY dolphin-emu-qt2) + +add_executable(${DOLPHINQT2_BINARY} ${SRCS} ${UI_HEADERS}) +target_link_libraries(${DOLPHINQT2_BINARY} ${LIBS} Qt5::Widgets) + +install(TARGETS ${DOLPHINQT2_BINARY} RUNTIME DESTINATION ${bindir}) diff --git a/Source/Core/DolphinQt2/GameList/GameFile.cpp b/Source/Core/DolphinQt2/GameList/GameFile.cpp new file mode 100644 index 0000000000..725e444067 --- /dev/null +++ b/Source/Core/DolphinQt2/GameList/GameFile.cpp @@ -0,0 +1,202 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/FileUtil.h" +#include "Core/ConfigManager.h" +#include "DiscIO/VolumeCreator.h" +#include "DolphinQt2/Resources.h" +#include "DolphinQt2/GameList/GameFile.h" + +static const int CACHE_VERSION = 13; // Last changed in PR #3261 +static const int DATASTREAM_VERSION = QDataStream::Qt_5_5; + +static QMap ConvertLanguageMap( + const std::map& map) +{ + QMap result; + for (auto entry : map) + result.insert(entry.first, QString::fromStdString(entry.second).trimmed()); + return result; +} + +GameFile::GameFile(QString path) : m_path(path) +{ + m_valid = false; + + if (!LoadFileInfo(path)) + return; + + if (!TryLoadCache()) + { + if (TryLoadVolume()) + { + LoadState(); + } + else if (!TryLoadElfDol()) + { + return; + } + } + + m_valid = true; +} + +DiscIO::IVolume::ELanguage GameFile::GetDefaultLanguage() const +{ + bool wii = m_platform != DiscIO::IVolume::GAMECUBE_DISC; + return SConfig::GetInstance().GetCurrentLanguage(wii); +} + +QString GameFile::GetCacheFileName() const +{ + QString folder = QString::fromStdString(File::GetUserPath(D_CACHE_IDX)); + // Append a hash of the full path to prevent name clashes between + // files with the same names in different folders. + QString hash = QString::fromUtf8( + QCryptographicHash::hash(m_path.toUtf8(), + QCryptographicHash::Md5).toHex()); + return folder + m_file_name + hash; +} + +void GameFile::ReadBanner(const DiscIO::IVolume& volume) +{ + int width, height; + std::vector buffer = volume.GetBanner(&width, &height); + QImage banner(width, height, QImage::Format_RGB888); + for (int i = 0; i < width * height; i++) + { + int x = i % width, y = i / width; + banner.setPixel(x, y, qRgb((buffer[i] & 0xFF0000) >> 16, + (buffer[i] & 0x00FF00) >> 8, + (buffer[i] & 0x0000FF) >> 0)); + } + + if (!banner.isNull()) + m_banner = QPixmap::fromImage(banner); + else + m_banner = Resources::GetMisc(Resources::BANNER_MISSING); +} + +bool GameFile::LoadFileInfo(QString path) +{ + QFileInfo info(path); + if (!info.exists() || !info.isReadable()) + return false; + + m_file_name = info.fileName(); + m_extension = info.suffix(); + m_folder = info.dir().dirName(); + m_last_modified = info.lastModified(); + m_size = info.size(); + + return true; +} + +void GameFile::LoadState() +{ + IniFile ini = SConfig::LoadGameIni(m_unique_id.toStdString(), m_revision); + std::string issues_temp; + ini.GetIfExists("EmuState", "EmulationStateId", &m_rating); + ini.GetIfExists("EmuState", "EmulationIssues", &issues_temp); + m_issues = QString::fromStdString(issues_temp); +} + +bool GameFile::IsElfOrDol() +{ + return m_extension == QStringLiteral("elf") || + m_extension == QStringLiteral("dol"); +} + +bool GameFile::TryLoadCache() +{ + QFile cache(GetCacheFileName()); + if (!cache.exists()) + return false; + if (!cache.open(QIODevice::ReadOnly)) + return false; + if (QFileInfo(cache).lastModified() < m_last_modified) + return false; + + QDataStream in(&cache); + in.setVersion(DATASTREAM_VERSION); + + int cache_version; + in >> cache_version; + if (cache_version != CACHE_VERSION) + return false; + + return false; +} + +bool GameFile::TryLoadVolume() +{ + QSharedPointer volume(DiscIO::CreateVolumeFromFilename(m_path.toStdString())); + if (volume == nullptr) + return false; + + m_unique_id = QString::fromStdString(volume->GetUniqueID()); + m_maker_id = QString::fromStdString(volume->GetMakerID()); + m_revision = volume->GetRevision(); + m_internal_name = QString::fromStdString(volume->GetInternalName()); + m_short_names = ConvertLanguageMap(volume->GetNames(false)); + m_long_names = ConvertLanguageMap(volume->GetNames(true)); + m_descriptions = ConvertLanguageMap(volume->GetDescriptions()); + m_company = QString::fromStdString(volume->GetCompany()); + m_disc_number = volume->GetDiscNumber(); + m_platform = volume->GetVolumeType(); + m_country = volume->GetCountry(); + m_blob_type = volume->GetBlobType(); + m_raw_size = volume->GetRawSize(); + + if (m_company.isEmpty() && m_unique_id.size() >= 6) + m_company = QString::fromStdString( + DiscIO::GetCompanyFromID(m_unique_id.mid(4, 2).toStdString())); + + ReadBanner(*volume); + + SaveCache(); + return true; +} + +bool GameFile::TryLoadElfDol() +{ + if (!IsElfOrDol()) + return false; + + m_revision = 0; + m_long_names[DiscIO::IVolume::LANGUAGE_ENGLISH] = m_file_name; + m_platform = DiscIO::IVolume::ELF_DOL; + m_country = DiscIO::IVolume::COUNTRY_UNKNOWN; + m_blob_type = DiscIO::BlobType::DIRECTORY; + m_raw_size = m_size; + m_banner = Resources::GetMisc(Resources::BANNER_MISSING); + m_rating = 0; + + return true; +} +void GameFile::SaveCache() +{ + // TODO +} + +QString GameFile::GetLanguageString(QMap m) const +{ + // Try the settings language, then English, then just pick one. + if (m.isEmpty()) + return QString(); + DiscIO::IVolume::ELanguage current_lang = GetDefaultLanguage(); + if (m.contains(current_lang)) + return m[current_lang]; + if (m.contains(DiscIO::IVolume::LANGUAGE_ENGLISH)) + return m[DiscIO::IVolume::LANGUAGE_ENGLISH]; + return m.first(); +} diff --git a/Source/Core/DolphinQt2/GameList/GameFile.h b/Source/Core/DolphinQt2/GameList/GameFile.h new file mode 100644 index 0000000000..8261fb1743 --- /dev/null +++ b/Source/Core/DolphinQt2/GameList/GameFile.h @@ -0,0 +1,101 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include "DiscIO/Volume.h" + +// TODO cache +class GameFile final +{ +public: + explicit GameFile(QString path); + + bool IsValid() const { return m_valid; } + + // These will be properly initialized before we try to load the file. + QString GetPath() const { return m_path; } + QString GetFileName() const { return m_file_name; } + QString GetExtension() const { return m_extension; } + QString GetFolder() const { return m_folder; } + qint64 GetFileSize() const { return m_size; } + + // The rest will not. + QString GetUniqueID() const { return m_unique_id; } + QString GetMakerID() const { return m_maker_id; } + u16 GetRevision() const { return m_revision; } + QString GetInternalName() const { return m_internal_name; } + QString GetCompany() const { return m_company; } + u8 GetDiscNumber() const { return m_disc_number; } + u64 GetRawSize() const { return m_raw_size; } + QPixmap GetBanner() const { return m_banner; } + QString GetIssues() const { return m_issues; } + int GetRating() const { return m_rating; } + + DiscIO::IVolume::EPlatform GetPlatform() const { return m_platform; } + DiscIO::IVolume::ECountry GetCountry() const { return m_country; } + DiscIO::BlobType GetBlobType() const { return m_blob_type; } + + QString GetShortName() const { return GetLanguageString(m_short_names); } + QString GetShortName(DiscIO::IVolume::ELanguage lang) const + { + return m_short_names[lang]; + } + + QString GetLongName() const { return GetLanguageString(m_long_names); } + QString GetLongName(DiscIO::IVolume::ELanguage lang) const + { + return m_long_names[lang]; + } + + QString GetDescription() const { return GetLanguageString(m_descriptions); } + QString GetDescription(DiscIO::IVolume::ELanguage lang) const + { + return m_descriptions[lang]; + } + +private: + DiscIO::IVolume::ELanguage GetDefaultLanguage() const; + QString GetLanguageString(QMap m) const; + + QString GetCacheFileName() const; + void ReadBanner(const DiscIO::IVolume& volume); + bool LoadFileInfo(QString path); + void LoadState(); + bool IsElfOrDol(); + bool TryLoadElfDol(); + bool TryLoadCache(); + bool TryLoadVolume(); + void SaveCache(); + + bool m_valid; + QString m_path; + QString m_file_name; + QString m_extension; + QString m_folder; + QDateTime m_last_modified; + qint64 m_size; + + QString m_unique_id; + QString m_maker_id; + u16 m_revision; + QString m_internal_name; + QMap m_short_names; + QMap m_long_names; + QMap m_descriptions; + QString m_company; + u8 m_disc_number; + DiscIO::IVolume::EPlatform m_platform; + DiscIO::IVolume::ECountry m_country; + DiscIO::BlobType m_blob_type; + u64 m_raw_size; + QPixmap m_banner; + QString m_issues; + int m_rating; +}; diff --git a/Source/Core/DolphinQt2/GameList/GameList.cpp b/Source/Core/DolphinQt2/GameList/GameList.cpp new file mode 100644 index 0000000000..6ee3ab1d45 --- /dev/null +++ b/Source/Core/DolphinQt2/GameList/GameList.cpp @@ -0,0 +1,88 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "Core/ConfigManager.h" +#include "DolphinQt2/GameList/GameList.h" + +GameList::GameList(QWidget* parent): QStackedWidget(parent) +{ + m_model = new GameListModel(this); + m_proxy = new QSortFilterProxyModel(this); + m_proxy->setSourceModel(m_model); + + MakeTableView(); + MakeListView(); + + connect(m_table, &QTableView::doubleClicked, this, &GameList::GameSelected); + connect(m_list, &QListView::doubleClicked, this, &GameList::GameSelected); + connect(this, &GameList::DirectoryAdded, m_model, &GameListModel::DirectoryAdded); + + addWidget(m_table); + addWidget(m_list); + setCurrentWidget(m_table); +} + +void GameList::MakeTableView() +{ + m_table = new QTableView(this); + m_table->setModel(m_proxy); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setAlternatingRowColors(true); + m_table->setShowGrid(false); + m_table->setSortingEnabled(true); + m_table->setCurrentIndex(QModelIndex()); + + // These fixed column widths make it so that the DisplayRole is cut + // off, which lets us see the icon but sort by the actual value. + // It's a bit of a hack. To do it right we need to subclass + // QSortFilterProxyModel and not show those items. + m_table->setColumnWidth(GameListModel::COL_PLATFORM, 52); + m_table->setColumnWidth(GameListModel::COL_COUNTRY, 38); + m_table->setColumnWidth(GameListModel::COL_RATING, 52); + m_table->setColumnHidden(GameListModel::COL_LARGE_ICON, true); + + m_table->horizontalHeader()->setSectionResizeMode( + GameListModel::COL_PLATFORM, QHeaderView::Fixed); + m_table->horizontalHeader()->setSectionResizeMode( + GameListModel::COL_COUNTRY, QHeaderView::Fixed); + m_table->horizontalHeader()->setSectionResizeMode( + GameListModel::COL_ID, QHeaderView::ResizeToContents); + m_table->horizontalHeader()->setSectionResizeMode( + GameListModel::COL_TITLE, QHeaderView::Stretch); + m_table->horizontalHeader()->setSectionResizeMode( + GameListModel::COL_MAKER, QHeaderView::ResizeToContents); + m_table->horizontalHeader()->setSectionResizeMode( + GameListModel::COL_SIZE, QHeaderView::ResizeToContents); + m_table->horizontalHeader()->setSectionResizeMode( + GameListModel::COL_DESCRIPTION, QHeaderView::Stretch); + m_table->horizontalHeader()->setSectionResizeMode( + GameListModel::COL_RATING, QHeaderView::Fixed); +} + +void GameList::MakeListView() +{ + m_list = new QListView(this); + m_list->setModel(m_proxy); + m_list->setViewMode(QListView::IconMode); + m_list->setModelColumn(GameListModel::COL_LARGE_ICON); + m_list->setResizeMode(QListView::Adjust); + m_list->setUniformItemSizes(true); +} + +QString GameList::GetSelectedGame() const +{ + QItemSelectionModel* sel_model; + if (currentWidget() == m_table) + sel_model = m_table->selectionModel(); + else + sel_model = m_list->selectionModel(); + + if (sel_model->hasSelection()) + return m_model->GetPath(m_proxy->mapToSource(sel_model->selectedIndexes()[0]).row()); + else + return QString(); +} diff --git a/Source/Core/DolphinQt2/GameList/GameList.h b/Source/Core/DolphinQt2/GameList/GameList.h new file mode 100644 index 0000000000..a52571e77c --- /dev/null +++ b/Source/Core/DolphinQt2/GameList/GameList.h @@ -0,0 +1,41 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include "DolphinQt2/GameList/GameFile.h" +#include "DolphinQt2/GameList/GameListModel.h" + +class GameList final : public QStackedWidget +{ + Q_OBJECT + +public: + explicit GameList(QWidget* parent = nullptr); + + QString GetSelectedGame() const; + +public slots: + void SetTableView() { setCurrentWidget(m_table); } + void SetListView() { setCurrentWidget(m_list); } + void SetViewColumn(int col, bool view) { m_table->setColumnHidden(col, !view); } + +signals: + void GameSelected(); + void DirectoryAdded(QString dir); + +private: + void MakeTableView(); + void MakeListView(); + + GameListModel* m_model; + QSortFilterProxyModel* m_proxy; + QListView* m_list; + QTableView* m_table; +}; diff --git a/Source/Core/DolphinQt2/GameList/GameListModel.cpp b/Source/Core/DolphinQt2/GameList/GameListModel.cpp new file mode 100644 index 0000000000..301bc9d40d --- /dev/null +++ b/Source/Core/DolphinQt2/GameList/GameListModel.cpp @@ -0,0 +1,132 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/Resources.h" +#include "DolphinQt2/GameList/GameListModel.h" + +static QString FormatSize(qint64 size) +{ + QStringList units{ + QStringLiteral("KB"), + QStringLiteral("MB"), + QStringLiteral("GB"), + QStringLiteral("TB") + }; + QStringListIterator i(units); + QString unit = QStringLiteral("B"); + double num = (double) size; + while (num > 1024.0 && i.hasNext()) + { + unit = i.next(); + num /= 1024.0; + } + return QStringLiteral("%1 %2").arg(QString::number(num, 'f', 1)).arg(unit); +} + +GameListModel::GameListModel(QObject* parent) + : QAbstractTableModel(parent) +{ + connect(&m_tracker, &GameTracker::GameLoaded, this, &GameListModel::UpdateGame); + connect(&m_tracker, &GameTracker::GameRemoved, this, &GameListModel::RemoveGame); + connect(this, &GameListModel::DirectoryAdded, &m_tracker, &GameTracker::AddDirectory); +} + +QVariant GameListModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + QSharedPointer game = m_games[index.row()]; + if (index.column() == COL_PLATFORM && role == Qt::DecorationRole) + return QVariant(Resources::GetPlatform(game->GetPlatform())); + else if (index.column() == COL_PLATFORM && role == Qt::DisplayRole) + return QVariant(game->GetPlatform()); + + else if (index.column() == COL_TITLE && role == Qt::DecorationRole) + return QVariant(game->GetBanner()); + else if (index.column() == COL_TITLE && role == Qt::DisplayRole) + return QVariant(game->GetLongName()); + + else if (index.column() == COL_ID && role == Qt::DisplayRole) + return QVariant(game->GetUniqueID()); + + else if (index.column() == COL_DESCRIPTION && role == Qt::DisplayRole) + return QVariant(game->GetDescription()); + + else if (index.column() == COL_MAKER && role == Qt::DisplayRole) + return QVariant(game->GetCompany()); + + // FIXME this sorts lexicographically, not by size. + else if (index.column() == COL_SIZE && role == Qt::DisplayRole) + return QVariant(FormatSize(game->GetFileSize())); + + else if (index.column() == COL_COUNTRY && role == Qt::DecorationRole) + return QVariant(Resources::GetCountry(game->GetCountry())); + else if (index.column() == COL_COUNTRY && role == Qt::DisplayRole) + return QVariant(game->GetCountry()); + + else if (index.column() == COL_RATING && role == Qt::DecorationRole) + return QVariant(Resources::GetRating(game->GetRating())); + else if (index.column() == COL_RATING && role == Qt::DisplayRole) + return QVariant(game->GetRating()); + + else if (index.column() == COL_LARGE_ICON && role == Qt::DecorationRole) + return QVariant(game->GetBanner().scaled(144, 48)); + else if (index.column() == COL_LARGE_ICON && role == Qt::DisplayRole) + return QVariant(game->GetLongName()); + + else + 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 COL_TITLE: return QVariant(tr("Title")); + case COL_ID: return QVariant(tr("ID")); + case COL_DESCRIPTION: return QVariant(tr("Description")); + case COL_MAKER: return QVariant(tr("Maker")); + case COL_SIZE: return QVariant(tr("Size")); + case COL_RATING: return QVariant(tr("Quality")); + default: return QVariant(); + } +} + +int GameListModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + return m_games.size(); +} + +int GameListModel::columnCount(const QModelIndex& parent) const +{ + return NUM_COLS; +} + + +void GameListModel::UpdateGame(QSharedPointer game) +{ + QString path = game->GetPath(); + if (m_entries.contains(path)) + RemoveGame(path); + + beginInsertRows(QModelIndex(), m_games.size(), m_games.size()); + m_entries[path] = m_games.size(); + m_games.append(game); + endInsertRows(); +} + +void GameListModel::RemoveGame(QString path) +{ + int entry = m_entries[path]; + beginRemoveRows(QModelIndex(), entry, entry); + m_entries.remove(path); + m_games.removeAt(entry); + endRemoveRows(); +} diff --git a/Source/Core/DolphinQt2/GameList/GameListModel.h b/Source/Core/DolphinQt2/GameList/GameListModel.h new file mode 100644 index 0000000000..98975738e7 --- /dev/null +++ b/Source/Core/DolphinQt2/GameList/GameListModel.h @@ -0,0 +1,55 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "DolphinQt2/GameList/GameFile.h" +#include "DolphinQt2/GameList/GameTracker.h" + +class GameListModel final : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit GameListModel(QObject* parent = nullptr); + + // Qt's Model/View stuff uses these overrides. + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex& parent) const; + int columnCount(const QModelIndex& parent) const; + + // Path of the Game at the specified index. + QString GetPath(int index) const { return m_games[index]->GetPath(); } + + enum + { + COL_PLATFORM = 0, + COL_ID, + COL_TITLE, + COL_DESCRIPTION, + COL_MAKER, + COL_SIZE, + COL_COUNTRY, + COL_RATING, + COL_LARGE_ICON, + NUM_COLS + }; + +public slots: + void UpdateGame(QSharedPointer game); + void RemoveGame(QString path); + +signals: + void DirectoryAdded(QString dir); + +private: + GameTracker m_tracker; + QList> m_games; + // Path -> index in m_games + QMap m_entries; +}; diff --git a/Source/Core/DolphinQt2/GameList/GameTracker.cpp b/Source/Core/DolphinQt2/GameList/GameTracker.cpp new file mode 100644 index 0000000000..5f56ad642d --- /dev/null +++ b/Source/Core/DolphinQt2/GameList/GameTracker.cpp @@ -0,0 +1,84 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include + +#include "Core/ConfigManager.h" +#include "DolphinQt2/GameList/GameTracker.h" + +GameTracker::GameTracker(QObject* parent) + : QFileSystemWatcher(parent) +{ + m_loader = new GameLoader; + m_loader->moveToThread(&m_loader_thread); + + qRegisterMetaType>(); + connect(&m_loader_thread, &QThread::finished, m_loader, &QObject::deleteLater); + connect(this, &QFileSystemWatcher::directoryChanged, this, &GameTracker::UpdateDirectory); + connect(this, &QFileSystemWatcher::fileChanged, this, &GameTracker::UpdateFile); + connect(this, &GameTracker::PathChanged, m_loader, &GameLoader::LoadGame); + connect(m_loader, &GameLoader::GameLoaded, this, &GameTracker::GameLoaded); + + GenerateFilters(); + + m_loader_thread.start(); + + for (const std::string& dir : SConfig::GetInstance().m_ISOFolder) + AddDirectory(QString::fromStdString(dir)); +} + +GameTracker::~GameTracker() +{ + m_loader_thread.quit(); + m_loader_thread.wait(); +} + +void GameTracker::AddDirectory(QString dir) +{ + addPath(dir); + UpdateDirectory(dir); +} + +void GameTracker::UpdateDirectory(QString dir) +{ + QDirIterator it(dir, m_filters); + while (it.hasNext()) + { + QString path = QFileInfo(it.next()).canonicalFilePath(); + if (!m_tracked_files.contains(path)) + { + addPath(path); + m_tracked_files.insert(path); + emit PathChanged(path); + } + } +} + +void GameTracker::UpdateFile(QString file) +{ + if (QFileInfo(file).exists()) + { + emit PathChanged(file); + } + else if (removePath(file)) + { + m_tracked_files.remove(file); + emit GameRemoved(file); + } +} + +void GameTracker::GenerateFilters() +{ + m_filters.clear(); + if (SConfig::GetInstance().m_ListGC) + m_filters << tr("*.gcm"); + if (SConfig::GetInstance().m_ListWii || SConfig::GetInstance().m_ListGC) + m_filters << tr("*.iso") << tr("*.ciso") << tr("*.gcz") << tr("*.wbfs"); + if (SConfig::GetInstance().m_ListWad) + m_filters << tr("*.wad"); + if (SConfig::GetInstance().m_ListElfDol) + m_filters << tr("*.elf") << tr("*.dol"); +} diff --git a/Source/Core/DolphinQt2/GameList/GameTracker.h b/Source/Core/DolphinQt2/GameList/GameTracker.h new file mode 100644 index 0000000000..f4f33be815 --- /dev/null +++ b/Source/Core/DolphinQt2/GameList/GameTracker.h @@ -0,0 +1,66 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "DolphinQt2/GameList/GameFile.h" +#include "DolphinQt2/GameList/GameTracker.h" + +class GameLoader; + +// Watches directories and loads GameFiles in a separate thread. +// To use this, just add directories using AddDirectory, and listen for the +// GameLoaded and GameRemoved signals. +class GameTracker final : public QFileSystemWatcher +{ + Q_OBJECT + +public: + explicit GameTracker(QObject* parent = nullptr); + ~GameTracker(); + +public slots: + void AddDirectory(QString dir); + +signals: + void GameLoaded(QSharedPointer game); + void GameRemoved(QString path); + + void PathChanged(QString path); + +private: + void UpdateDirectory(QString dir); + void UpdateFile(QString path); + void GenerateFilters(); + + QSet m_tracked_files; + QStringList m_filters; + QThread m_loader_thread; + GameLoader* m_loader; +}; + +class GameLoader : public QObject +{ + Q_OBJECT + +public slots: + void LoadGame(QString path) + { + GameFile* game = new GameFile(path); + if (game->IsValid()) + emit GameLoaded(QSharedPointer(game)); + } + +signals: + void GameLoaded(QSharedPointer game); +}; + +Q_DECLARE_METATYPE(QSharedPointer) diff --git a/Source/Core/DolphinQt2/Host.cpp b/Source/Core/DolphinQt2/Host.cpp new file mode 100644 index 0000000000..389e0508b8 --- /dev/null +++ b/Source/Core/DolphinQt2/Host.cpp @@ -0,0 +1,96 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "Common/Common.h" +#include "Core/Host.h" +#include "DolphinQt2/Host.h" +#include "DolphinQt2/MainWindow.h" + +Host* Host::m_instance = nullptr; + +Host* Host::GetInstance() +{ + if (m_instance == nullptr) + m_instance = new Host(); + return m_instance; +} + +void* Host::GetRenderHandle() +{ + QMutexLocker locker(&m_lock); + return m_render_handle; +} + +void Host::SetRenderHandle(void* handle) +{ + QMutexLocker locker(&m_lock); + m_render_handle = handle; +} + +bool Host::GetRenderFocus() +{ + QMutexLocker locker(&m_lock); + return m_render_focus; +} + +void Host::SetRenderFocus(bool focus) +{ + QMutexLocker locker(&m_lock); + m_render_focus = focus; +} + +bool Host::GetRenderFullscreen() +{ + QMutexLocker locker(&m_lock); + return m_render_fullscreen; +} + +void Host::SetRenderFullscreen(bool fullscreen) +{ + QMutexLocker locker(&m_lock); + m_render_fullscreen = fullscreen; +} + +void Host_Message(int id) +{ + if (id == WM_USER_STOP) + emit Host::GetInstance()->RequestStop(); +} + +void Host_UpdateTitle(const std::string& title) +{ + emit Host::GetInstance()->RequestTitle(QString::fromStdString(title)); +} + +void* Host_GetRenderHandle() +{ + return Host::GetInstance()->GetRenderHandle(); +} + +bool Host_RendererHasFocus() +{ + return Host::GetInstance()->GetRenderFocus(); +} + +bool Host_RendererIsFullscreen() { + return Host::GetInstance()->GetRenderFullscreen(); +} + +// We ignore these, and their purpose should be questioned individually. +// In particular, RequestRenderWindowSize, RequestFullscreen, and +// UpdateMainFrame should almost certainly be removed. +void Host_UpdateMainFrame() {} +void Host_RequestFullscreen(bool enable) {} +void Host_RequestRenderWindowSize(int w, int h) {} +bool Host_UIHasFocus() { return false; } +void Host_NotifyMapLoaded() {} +void Host_UpdateDisasmDialog() {} +void Host_SetStartupDebuggingParameters() {} +void Host_SetWiiMoteConnectionState(int state) {} +void Host_ConnectWiimote(int wm_idx, bool connect) {} +void Host_ShowVideoConfig(void* parent, const std::string& backend_name, + const std::string& config_name) {} +void Host_RefreshDSPDebuggerWindow() {} diff --git a/Source/Core/DolphinQt2/Host.h b/Source/Core/DolphinQt2/Host.h new file mode 100644 index 0000000000..fb10403535 --- /dev/null +++ b/Source/Core/DolphinQt2/Host.h @@ -0,0 +1,45 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +// Singleton that talks to the Core via the interface defined in Core/Host.h. +// Because Host_* calls might come from different threads than the MainWindow, +// the Host class communicates with it via signals/slots only. + +// Many of the Host_* functions are ignored, and some shouldn't exist. +class Host final : public QObject +{ + Q_OBJECT + +public: + static Host* GetInstance(); + + void* GetRenderHandle(); + bool GetRenderFocus(); + bool GetRenderFullscreen(); + +public slots: + void SetRenderHandle(void* handle); + void SetRenderFocus(bool focus); + void SetRenderFullscreen(bool fullscreen); + +signals: + void RequestTitle(QString title); + void RequestStop(); + void RequestRenderSize(int w, int h); + +private: + Host() {} + static Host* m_instance; + QMutex m_lock; + + void* m_render_handle; + bool m_render_focus; + bool m_render_fullscreen; +}; diff --git a/Source/Core/DolphinQt2/Main.cpp b/Source/Core/DolphinQt2/Main.cpp new file mode 100644 index 0000000000..639f68a4b4 --- /dev/null +++ b/Source/Core/DolphinQt2/Main.cpp @@ -0,0 +1,33 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "Core/BootManager.h" +#include "Core/Core.h" +#include "DolphinQt2/Host.h" +#include "DolphinQt2/MainWindow.h" +#include "DolphinQt2/Resources.h" +#include "UICommon/UICommon.h" + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + + UICommon::SetUserDirectory(""); + UICommon::CreateDirectories(); + UICommon::Init(); + Resources::Init(); + + MainWindow win; + win.show(); + int retval = app.exec(); + + BootManager::Stop(); + Core::Shutdown(); + UICommon::Shutdown(); + Host::GetInstance()->deleteLater(); + + return retval; +} diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp new file mode 100644 index 0000000000..49e4ae4511 --- /dev/null +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -0,0 +1,404 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common/FileUtil.h" +#include "Core/BootManager.h" +#include "Core/ConfigManager.h" +#include "Core/Core.h" +#include "DolphinQt2/Host.h" +#include "DolphinQt2/MainWindow.h" +#include "DolphinQt2/Resources.h" +#include "DolphinQt2/GameList/GameListModel.h" + +MainWindow::MainWindow() : QMainWindow(nullptr) +{ + setWindowTitle(tr("Dolphin")); + setWindowIcon(QIcon(Resources::GetMisc(Resources::LOGO_SMALL))); + + MakeToolBar(); + MakeGameList(); + MakeRenderWidget(); + MakeStack(); + MakeMenus(); +} + +MainWindow::~MainWindow() +{ + m_render_widget->deleteLater(); +} + +void MainWindow::MakeMenus() +{ + MakeFileMenu(); + menuBar()->addMenu(tr("Emulation")); + menuBar()->addMenu(tr("Movie")); + menuBar()->addMenu(tr("Options")); + menuBar()->addMenu(tr("Tools")); + MakeViewMenu(); + menuBar()->addMenu(tr("Help")); +} + +void MainWindow::MakeFileMenu() +{ + QMenu* file_menu = menuBar()->addMenu(tr("File")); + file_menu->addAction(tr("Open"), this, SLOT(Open())); + file_menu->addAction(tr("Exit"), this, SLOT(close())); +} + +void MainWindow::MakeViewMenu() +{ + QMenu* view_menu = menuBar()->addMenu(tr("View")); + AddTableColumnsMenu(view_menu); + AddListTypePicker(view_menu); +} + +void MainWindow::AddTableColumnsMenu(QMenu* view_menu) +{ + QActionGroup* column_group = new QActionGroup(this); + QMenu* cols_menu = view_menu->addMenu(tr("Table Columns")); + column_group->setExclusive(false); + + QStringList col_names{ + tr("Platform"), + tr("ID"), + tr("Title"), + tr("Description"), + tr("Maker"), + tr("Size"), + tr("Country"), + tr("Quality") + }; + // TODO we'll need to update SConfig with another column. Then we can clean this + // up significantly. + QList show_cols{ + SConfig::GetInstance().m_showSystemColumn, + SConfig::GetInstance().m_showIDColumn, + SConfig::GetInstance().m_showBannerColumn, + false, + SConfig::GetInstance().m_showMakerColumn, + SConfig::GetInstance().m_showSizeColumn, + SConfig::GetInstance().m_showRegionColumn, + SConfig::GetInstance().m_showStateColumn, + }; + // - 1 because we never show COL_LARGE_ICON column in the table. + for (int i = 0; i < GameListModel::NUM_COLS - 1; i++) + { + QAction* action = column_group->addAction(cols_menu->addAction(col_names[i])); + action->setCheckable(true); + action->setChecked(show_cols[i]); + m_game_list->SetViewColumn(i, show_cols[i]); + connect(action, &QAction::triggered, [=]() + { + m_game_list->SetViewColumn(i, action->isChecked()); + switch (i) + { + case GameListModel::COL_PLATFORM: + SConfig::GetInstance().m_showSystemColumn = action->isChecked(); + break; + case GameListModel::COL_ID: + SConfig::GetInstance().m_showIDColumn = action->isChecked(); + break; + case GameListModel::COL_TITLE: + SConfig::GetInstance().m_showBannerColumn = action->isChecked(); + break; + case GameListModel::COL_MAKER: + SConfig::GetInstance().m_showMakerColumn = action->isChecked(); + break; + case GameListModel::COL_SIZE: + SConfig::GetInstance().m_showSizeColumn = action->isChecked(); + break; + case GameListModel::COL_COUNTRY: + SConfig::GetInstance().m_showRegionColumn = action->isChecked(); + break; + case GameListModel::COL_RATING: + SConfig::GetInstance().m_showStateColumn = action->isChecked(); + break; + default: break; + } + SConfig::GetInstance().SaveSettings(); + }); + } +} + +void MainWindow::AddListTypePicker(QMenu* view_menu) +{ + QActionGroup* list_group = new QActionGroup(this); + view_menu->addSection(tr("List Type")); + list_group->setExclusive(true); + + QAction* set_table = list_group->addAction(view_menu->addAction(tr("Table"))); + QAction* set_list = list_group->addAction(view_menu->addAction(tr("List"))); + + set_table->setCheckable(true); + set_table->setChecked(true); + set_list->setCheckable(true); + + connect(set_table, &QAction::triggered, m_game_list, &GameList::SetTableView); + connect(set_list, &QAction::triggered, m_game_list, &GameList::SetListView); +} + +void MainWindow::MakeToolBar() +{ + setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + m_tool_bar = addToolBar(tr("ToolBar")); + m_tool_bar->setMovable(false); + m_tool_bar->setFloatable(false); + + PopulateToolBar(); +} + +void MainWindow::MakeGameList() +{ + m_game_list = new GameList(this); + connect(m_game_list, &GameList::GameSelected, this, &MainWindow::Play); +} + +void MainWindow::MakeRenderWidget() +{ + m_render_widget = new RenderWidget; + connect(m_render_widget, &RenderWidget::EscapePressed, this, &MainWindow::Stop); + connect(m_render_widget, &RenderWidget::Closed, this, &MainWindow::ForceStop); + m_render_widget->hide(); + m_rendering_to_main = false; +} + +void MainWindow::MakeStack() +{ + m_stack = new QStackedWidget; + m_stack->setMinimumSize(800, 600); + m_stack->addWidget(m_game_list); + setCentralWidget(m_stack); +} + +void MainWindow::PopulateToolBar() +{ + m_tool_bar->clear(); + + QString dir = QString::fromStdString(File::GetThemeDir(SConfig::GetInstance().theme_name)); + m_tool_bar->addAction( + QIcon(dir + QStringLiteral("open.png")), tr("Open"), + this, SLOT(Open())); + m_tool_bar->addAction( + QIcon(dir + QStringLiteral("browse.png")), tr("Browse"), + this, SLOT(Browse())); + + m_tool_bar->addSeparator(); + + QAction* play_action = m_tool_bar->addAction( + QIcon(dir + QStringLiteral("play.png")), tr("Play"), + this, SLOT(Play())); + connect(this, &MainWindow::EmulationStarted, [=](){ play_action->setEnabled(false); }); + connect(this, &MainWindow::EmulationPaused, [=](){ play_action->setEnabled(true); }); + connect(this, &MainWindow::EmulationStopped, [=](){ play_action->setEnabled(true); }); + + QAction* pause_action = m_tool_bar->addAction( + QIcon(dir + QStringLiteral("pause.png")), tr("Pause"), + this, SLOT(Pause())); + pause_action->setEnabled(false); + connect(this, &MainWindow::EmulationStarted, [=](){ pause_action->setEnabled(true); }); + connect(this, &MainWindow::EmulationPaused, [=](){ pause_action->setEnabled(false); }); + connect(this, &MainWindow::EmulationStopped, [=](){ pause_action->setEnabled(false); }); + + QAction* stop_action = m_tool_bar->addAction( + QIcon(dir + QStringLiteral("stop.png")), tr("Stop"), + this, SLOT(Stop())); + stop_action->setEnabled(false); + connect(this, &MainWindow::EmulationStarted, [=](){ stop_action->setEnabled(true); }); + connect(this, &MainWindow::EmulationPaused, [=](){ stop_action->setEnabled(true); }); + connect(this, &MainWindow::EmulationStopped, [=](){ stop_action->setEnabled(false); }); + + QAction* fullscreen_action = m_tool_bar->addAction( + QIcon(dir + QStringLiteral("fullscreen.png")), tr("Full Screen"), + this, SLOT(FullScreen())); + fullscreen_action->setEnabled(false); + connect(this, &MainWindow::EmulationStarted, [=](){ fullscreen_action->setEnabled(true); }); + connect(this, &MainWindow::EmulationStopped, [=](){ fullscreen_action->setEnabled(false); }); + + QAction* screenshot_action = m_tool_bar->addAction( + QIcon(dir + QStringLiteral("screenshot.png")), tr("Screen Shot"), + this, SLOT(ScreenShot())); + screenshot_action->setEnabled(false); + connect(this, &MainWindow::EmulationStarted, [=](){ screenshot_action->setEnabled(true); }); + connect(this, &MainWindow::EmulationStopped, [=](){ screenshot_action->setEnabled(false); }); + + m_tool_bar->addSeparator(); + m_tool_bar->addAction(QIcon(dir + QStringLiteral("config.png")), tr("Settings"))->setEnabled(false); + m_tool_bar->addAction(QIcon(dir + QStringLiteral("graphics.png")), tr("Graphics"))->setEnabled(false); + m_tool_bar->addAction(QIcon(dir + QStringLiteral("classic.png")), tr("Controllers"))->setEnabled(false); +} + +void MainWindow::Open() +{ + QString file = QFileDialog::getOpenFileName(this, + tr("Select a File"), + QDir::currentPath(), + tr("All GC/Wii files (*.elf *.dol *.gcm *.iso *.wbfs *.ciso *.gcz *.wad);;" + "All Files (*)")); + if (!file.isEmpty()) + StartGame(file); +} + +void MainWindow::Browse() +{ + QString dir = QFileDialog::getExistingDirectory(this, + tr("Select a Directory"), + QDir::currentPath()); + if (!dir.isEmpty()) + { + std::vector& iso_folders = SConfig::GetInstance().m_ISOFolder; + auto found = std::find(iso_folders.begin(), iso_folders.end(), dir.toStdString()); + if (found == iso_folders.end()) + { + iso_folders.push_back(dir.toStdString()); + SConfig::GetInstance().SaveSettings(); + emit m_game_list->DirectoryAdded(dir); + } + } +} + +void MainWindow::Play() +{ + // If we're in a paused game, start it up again. + // Otherwise, play the selected game, if there is one. + // Otherwise, play the last played game, if there is one. + // Otherwise, prompt for a new game. + if (Core::GetState() == Core::CORE_PAUSE) + { + Core::SetState(Core::CORE_RUN); + emit EmulationStarted(); + } + else + { + QString selection = m_game_list->GetSelectedGame(); + if (selection.length() > 0) + { + StartGame(selection); + } + else + { + QString path = QString::fromStdString(SConfig::GetInstance().m_LastFilename); + if (QFile::exists(path)) + StartGame(path); + else + Open(); + } + } +} + +void MainWindow::Pause() +{ + Core::SetState(Core::CORE_PAUSE); + emit EmulationPaused(); +} + +bool MainWindow::Stop() +{ + bool stop = true; + if (SConfig::GetInstance().bConfirmStop) + { + // We could pause the game here and resume it if they say no. + QMessageBox::StandardButton confirm; + confirm = QMessageBox::question(m_render_widget, tr("Confirm"), tr("Stop emulation?")); + stop = (confirm == QMessageBox::Yes); + } + + if (stop) + ForceStop(); + + return stop; +} + +void MainWindow::ForceStop() +{ + BootManager::Stop(); + HideRenderWidget(); + emit EmulationStopped(); +} + +void MainWindow::FullScreen() +{ + // If the render widget is fullscreen we want to reset it to whatever is in + // SConfig. If it's set to be fullscreen then it just remakes the window, + // which probably isn't ideal. + bool was_fullscreen = m_render_widget->isFullScreen(); + HideRenderWidget(); + if (was_fullscreen) + ShowRenderWidget(); + else + m_render_widget->showFullScreen(); +} + +void MainWindow::ScreenShot() +{ + Core::SaveScreenShot(); +} + +void MainWindow::StartGame(QString path) +{ + // If we're running, only start a new game once we've stopped the last. + if (Core::GetState() != Core::CORE_UNINITIALIZED) + { + if (!Stop()) + return; + } + // Boot up, show an error if it fails to load the game. + if (!BootManager::BootCore(path.toStdString())) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to init core"), QMessageBox::Ok); + return; + } + ShowRenderWidget(); + emit EmulationStarted(); +} + +void MainWindow::ShowRenderWidget() +{ + if (SConfig::GetInstance().bRenderToMain) + { + // If we're rendering to main, add it to the stack and update our title when necessary. + m_rendering_to_main = true; + m_stack->setCurrentIndex(m_stack->addWidget(m_render_widget)); + connect(Host::GetInstance(), &Host::RequestTitle, this, &MainWindow::setWindowTitle); + } + else + { + // Otherwise, just show it. + m_rendering_to_main = false; + if (SConfig::GetInstance().bFullscreen) + { + m_render_widget->showFullScreen(); + } + else + { + m_render_widget->setFixedSize( + SConfig::GetInstance().iRenderWindowWidth, + SConfig::GetInstance().iRenderWindowHeight); + m_render_widget->showNormal(); + } + } +} + +void MainWindow::HideRenderWidget() +{ + if (m_rendering_to_main) + { + // Remove the widget from the stack and reparent it to nullptr, so that it can draw + // itself in a new window if it wants. Disconnect the title updates. + m_stack->removeWidget(m_render_widget); + m_render_widget->setParent(nullptr); + m_rendering_to_main = false; + disconnect(Host::GetInstance(), &Host::RequestTitle, this, &MainWindow::setWindowTitle); + setWindowTitle(tr("Dolphin")); + } + m_render_widget->hide(); +} diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h new file mode 100644 index 0000000000..99ed25f054 --- /dev/null +++ b/Source/Core/DolphinQt2/MainWindow.h @@ -0,0 +1,61 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include "DolphinQt2/RenderWidget.h" +#include "DolphinQt2/GameList/GameList.h" + +class MainWindow final : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(); + ~MainWindow(); + +signals: + void EmulationStarted(); + void EmulationPaused(); + void EmulationStopped(); + +private slots: + void Open(); + void Browse(); + void Play(); + void Pause(); + bool Stop(); + void ForceStop(); + void FullScreen(); + void ScreenShot(); + +private: + void MakeToolBar(); + void MakeStack(); + void MakeGameList(); + void MakeRenderWidget(); + + void MakeMenus(); + void MakeFileMenu(); + void MakeViewMenu(); + void AddTableColumnsMenu(QMenu* view_menu); + void AddListTypePicker(QMenu* view_menu); + + void PopulateToolBar(); + + void StartGame(QString path); + void ShowRenderWidget(); + void HideRenderWidget(); + + QStackedWidget* m_stack; + QToolBar* m_tool_bar; + GameList* m_game_list; + RenderWidget* m_render_widget; + bool m_rendering_to_main; +}; diff --git a/Source/Core/DolphinQt2/RenderWidget.cpp b/Source/Core/DolphinQt2/RenderWidget.cpp new file mode 100644 index 0000000000..bacaf00f7c --- /dev/null +++ b/Source/Core/DolphinQt2/RenderWidget.cpp @@ -0,0 +1,51 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "DolphinQt2/Host.h" +#include "DolphinQt2/RenderWidget.h" + +RenderWidget::RenderWidget(QWidget* parent) + : QWidget(parent) +{ + setAttribute(Qt::WA_OpaquePaintEvent, true); + setAttribute(Qt::WA_NoSystemBackground, true); + + connect(Host::GetInstance(), &Host::RequestTitle, this, &RenderWidget::setWindowTitle); + connect(this, &RenderWidget::FocusChanged, Host::GetInstance(), &Host::SetRenderFocus); + connect(this, &RenderWidget::StateChanged, Host::GetInstance(), &Host::SetRenderFullscreen); + connect(this, &RenderWidget::HandleChanged, Host::GetInstance(), &Host::SetRenderHandle); + emit HandleChanged((void*) winId()); +} + +bool RenderWidget::event(QEvent* event) +{ + switch (event->type()) + { + case QEvent::KeyPress: + { + QKeyEvent* ke = static_cast(event); + if (ke->key() == Qt::Key_Escape) + emit EscapePressed(); + break; + } + case QEvent::WinIdChange: + emit HandleChanged((void*) winId()); + break; + case QEvent::FocusIn: + case QEvent::FocusOut: + emit FocusChanged(hasFocus()); + break; + case QEvent::WindowStateChange: + emit StateChanged(isFullScreen()); + break; + case QEvent::Close: + emit Closed(); + break; + default: + break; + } + return QWidget::event(event); +} diff --git a/Source/Core/DolphinQt2/RenderWidget.h b/Source/Core/DolphinQt2/RenderWidget.h new file mode 100644 index 0000000000..34da5afbbb --- /dev/null +++ b/Source/Core/DolphinQt2/RenderWidget.h @@ -0,0 +1,25 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +class RenderWidget final : public QWidget +{ + Q_OBJECT + +public: + RenderWidget(QWidget* parent = nullptr); + + bool event(QEvent* event); + +signals: + void EscapePressed(); + void Closed(); + void HandleChanged(void* handle); + void FocusChanged(bool focus); + void StateChanged(bool fullscreen); +}; diff --git a/Source/Core/DolphinQt2/Resources.cpp b/Source/Core/DolphinQt2/Resources.cpp new file mode 100644 index 0000000000..05b1014252 --- /dev/null +++ b/Source/Core/DolphinQt2/Resources.cpp @@ -0,0 +1,83 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "Common/FileUtil.h" +#include "Core/ConfigManager.h" +#include "DolphinQt2/Resources.h" + +QList Resources::m_platforms; +QList Resources::m_countries; +QList Resources::m_ratings; +QList Resources::m_misc; + +void Resources::Init() +{ + QString sys_dir = QString::fromStdString(File::GetSysDirectory() + "Resources/"); + + QStringList platforms{ + QStringLiteral("Platform_Gamecube.png"), + QStringLiteral("Platform_Wii.png"), + QStringLiteral("Platform_Wad.png"), + QStringLiteral("Platform_Wii.png") + }; + for (QString platform : platforms) + m_platforms.append(QPixmap(platform.prepend(sys_dir))); + + QStringList countries{ + QStringLiteral("Flag_Europe.png"), + QStringLiteral("Flag_Japan.png"), + QStringLiteral("Flag_USA.png"), + QStringLiteral("Flag_Australia.png"), + QStringLiteral("Flag_France.png"), + QStringLiteral("Flag_Germany.png"), + QStringLiteral("Flag_Italy.png"), + QStringLiteral("Flag_Korea.png"), + QStringLiteral("Flag_Netherlands.png"), + QStringLiteral("Flag_Russia.png"), + QStringLiteral("Flag_Spain.png"), + QStringLiteral("Flag_Taiwan.png"), + QStringLiteral("Flag_International.png"), + QStringLiteral("Flag_Unknown.png") + }; + for (QString country : countries) + m_countries.append(QPixmap(country.prepend(sys_dir))); + + QStringList ratings{ + QStringLiteral("rating0.png"), + QStringLiteral("rating1.png"), + QStringLiteral("rating2.png"), + QStringLiteral("rating3.png"), + QStringLiteral("rating4.png"), + QStringLiteral("rating5.png") + }; + for (QString rating : ratings) + m_ratings.append(QPixmap(rating.prepend(sys_dir))); + + QString theme_dir = QString::fromStdString(File::GetThemeDir(SConfig::GetInstance().theme_name)); + m_misc.append(QPixmap(QStringLiteral("nobanner.png").prepend(theme_dir))); + m_misc.append(QPixmap(QStringLiteral("dolphin_logo.png").prepend(theme_dir))); + m_misc.append(QPixmap(QStringLiteral("Dolphin.png").prepend(theme_dir))); +} + +QPixmap Resources::GetPlatform(int platform) +{ + return m_platforms[platform]; +} + +QPixmap Resources::GetCountry(int country) +{ + return m_countries[country]; +} + +QPixmap Resources::GetRating(int rating) +{ + return m_ratings[rating]; +} + +QPixmap Resources::GetMisc(int id) +{ + return m_misc[id]; +} diff --git a/Source/Core/DolphinQt2/Resources.h b/Source/Core/DolphinQt2/Resources.h new file mode 100644 index 0000000000..322f7880b3 --- /dev/null +++ b/Source/Core/DolphinQt2/Resources.h @@ -0,0 +1,36 @@ +// Copyright 2015 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +// Store for various QPixmaps that will be used repeatedly. +class Resources final +{ +public: + static void Init(); + + static QPixmap GetPlatform(int platform); + static QPixmap GetCountry(int country); + static QPixmap GetRating(int rating); + + static QPixmap GetMisc(int id); + + enum + { + BANNER_MISSING, + LOGO_LARGE, + LOGO_SMALL + }; + +private: + Resources() {} + + static QList m_platforms; + static QList m_countries; + static QList m_ratings; + static QList m_misc; +};