dolphin/Source/Core/DolphinWX/GameListCtrl.cpp
JosJuice 301218a103 Volume: Use more appropriate types for some returned values
Disc number is changed from bool to u8, and revision is changed from
int to u16 (WADs can use all 16 bits, but discs can only use 8 bits).
2015-05-29 21:14:02 +02:00

1326 lines
38 KiB
C++

// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <cinttypes>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
#include <wx/bitmap.h>
#include <wx/buffer.h>
#include <wx/colour.h>
#include <wx/dirdlg.h>
#include <wx/filedlg.h>
#include <wx/filefn.h>
#include <wx/filename.h>
#include <wx/imaglist.h>
#include <wx/listctrl.h>
#include <wx/menu.h>
#include <wx/msgdlg.h>
#include <wx/progdlg.h>
#include <wx/settings.h>
#include <wx/tipwin.h>
#include "Common/CDUtils.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
#include "Common/FileSearch.h"
#include "Common/FileUtil.h"
#include "Common/MathUtil.h"
#include "Common/StdMakeUnique.h"
#include "Common/StringUtil.h"
#include "Common/SysConf.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/CoreParameter.h"
#include "Core/Movie.h"
#include "Core/Boot/Boot.h"
#include "Core/HW/DVDInterface.h"
#include "Core/HW/WiiSaveCrypted.h"
#include "DiscIO/Blob.h"
#include "DiscIO/Volume.h"
#include "DiscIO/VolumeCreator.h"
#include "DolphinWX/Frame.h"
#include "DolphinWX/GameListCtrl.h"
#include "DolphinWX/Globals.h"
#include "DolphinWX/ISOFile.h"
#include "DolphinWX/ISOProperties.h"
#include "DolphinWX/Main.h"
#include "DolphinWX/WxUtils.h"
#include "DolphinWX/resources/Flag_Australia.xpm"
#include "DolphinWX/resources/Flag_Europe.xpm"
#include "DolphinWX/resources/Flag_France.xpm"
#include "DolphinWX/resources/Flag_Germany.xpm"
#include "DolphinWX/resources/Flag_Italy.xpm"
#include "DolphinWX/resources/Flag_Japan.xpm"
#include "DolphinWX/resources/Flag_Korea.xpm"
#include "DolphinWX/resources/Flag_Netherlands.xpm"
#include "DolphinWX/resources/Flag_Russia.xpm"
#include "DolphinWX/resources/Flag_Spain.xpm"
#include "DolphinWX/resources/Flag_Taiwan.xpm"
#include "DolphinWX/resources/Flag_Unknown.xpm"
#include "DolphinWX/resources/Flag_USA.xpm"
#include "DolphinWX/resources/Platform_Gamecube.xpm"
#include "DolphinWX/resources/Platform_Wad.xpm"
#include "DolphinWX/resources/Platform_Wii.xpm"
#include "DolphinWX/resources/rating_gamelist.h"
size_t CGameListCtrl::m_currentItem = 0;
size_t CGameListCtrl::m_numberItem = 0;
std::string CGameListCtrl::m_currentFilename;
static bool sorted = false;
static int CompareGameListItems(const GameListItem* iso1, const GameListItem* iso2,
long sortData = CGameListCtrl::COLUMN_TITLE)
{
int t = 1;
if (sortData < 0)
{
t = -1;
sortData = -sortData;
}
DiscIO::IVolume::ELanguage languageOne = SConfig::GetInstance().m_LocalCoreStartupParameter.GetCurrentLanguage(iso1->GetPlatform() != GameListItem::GAMECUBE_DISC);
DiscIO::IVolume::ELanguage languageOther = SConfig::GetInstance().m_LocalCoreStartupParameter.GetCurrentLanguage(iso2->GetPlatform() != GameListItem::GAMECUBE_DISC);
switch (sortData)
{
case CGameListCtrl::COLUMN_TITLE:
if (!strcasecmp(iso1->GetName(languageOne).c_str(), iso2->GetName(languageOther).c_str()))
{
if (iso1->GetUniqueID() != iso2->GetUniqueID())
return t * (iso1->GetUniqueID() > iso2->GetUniqueID() ? 1 : -1);
if (iso1->GetRevision() != iso2->GetRevision())
return t * (iso1->GetRevision() > iso2->GetRevision() ? 1 : -1);
if (iso1->GetDiscNumber() != iso2->GetDiscNumber())
return t * (iso1->GetDiscNumber() > iso2->GetDiscNumber() ? 1 : -1);
}
return strcasecmp(iso1->GetName(languageOne).c_str(),
iso2->GetName(languageOther).c_str()) * t;
case CGameListCtrl::COLUMN_MAKER:
return strcasecmp(iso1->GetCompany().c_str(), iso2->GetCompany().c_str()) * t;
case CGameListCtrl::COLUMN_ID:
return strcasecmp(iso1->GetUniqueID().c_str(), iso2->GetUniqueID().c_str()) * t;
case CGameListCtrl::COLUMN_COUNTRY:
if (iso1->GetCountry() > iso2->GetCountry())
return 1 * t;
if (iso1->GetCountry() < iso2->GetCountry())
return -1 * t;
return 0;
case CGameListCtrl::COLUMN_SIZE:
if (iso1->GetFileSize() > iso2->GetFileSize())
return 1 * t;
if (iso1->GetFileSize() < iso2->GetFileSize())
return -1 * t;
return 0;
case CGameListCtrl::COLUMN_PLATFORM:
if (iso1->GetPlatform() > iso2->GetPlatform())
return 1 * t;
if (iso1->GetPlatform() < iso2->GetPlatform())
return -1 * t;
return 0;
case CGameListCtrl::COLUMN_EMULATION_STATE:
{
const int
nState1 = iso1->GetEmuState(),
nState2 = iso2->GetEmuState();
if (nState1 > nState2)
return 1 * t;
if (nState1 < nState2)
return -1 * t;
else
return 0;
}
break;
}
return 0;
}
CGameListCtrl::CGameListCtrl(wxWindow* parent, const wxWindowID id, const
wxPoint& pos, const wxSize& size, long style)
: wxListCtrl(parent, id, pos, size, style), toolTip(nullptr)
{
Bind(wxEVT_SIZE, &CGameListCtrl::OnSize, this);
Bind(wxEVT_RIGHT_DOWN, &CGameListCtrl::OnRightClick, this);
Bind(wxEVT_LEFT_DOWN, &CGameListCtrl::OnLeftClick, this);
Bind(wxEVT_MOTION, &CGameListCtrl::OnMouseMotion, this);
Bind(wxEVT_LIST_KEY_DOWN, &CGameListCtrl::OnKeyPress, this);
Bind(wxEVT_LIST_COL_BEGIN_DRAG, &CGameListCtrl::OnColBeginDrag, this);
Bind(wxEVT_LIST_COL_CLICK, &CGameListCtrl::OnColumnClick, this);
Bind(wxEVT_MENU, &CGameListCtrl::OnProperties, this, IDM_PROPERTIES);
Bind(wxEVT_MENU, &CGameListCtrl::OnWiki, this, IDM_GAME_WIKI);
Bind(wxEVT_MENU, &CGameListCtrl::OnOpenContainingFolder, this, IDM_OPEN_CONTAINING_FOLDER);
Bind(wxEVT_MENU, &CGameListCtrl::OnOpenSaveFolder, this, IDM_OPEN_SAVE_FOLDER);
Bind(wxEVT_MENU, &CGameListCtrl::OnExportSave, this, IDM_EXPORT_SAVE);
Bind(wxEVT_MENU, &CGameListCtrl::OnSetDefaultISO, this, IDM_SET_DEFAULT_ISO);
Bind(wxEVT_MENU, &CGameListCtrl::OnCompressISO, this, IDM_COMPRESS_ISO);
Bind(wxEVT_MENU, &CGameListCtrl::OnMultiCompressISO, this, IDM_MULTI_COMPRESS_ISO);
Bind(wxEVT_MENU, &CGameListCtrl::OnMultiDecompressISO, this, IDM_MULTI_DECOMPRESS_ISO);
Bind(wxEVT_MENU, &CGameListCtrl::OnDeleteISO, this, IDM_DELETE_ISO);
Bind(wxEVT_MENU, &CGameListCtrl::OnChangeDisc, this, IDM_LIST_CHANGE_DISC);
}
CGameListCtrl::~CGameListCtrl()
{
if (m_imageListSmall)
delete m_imageListSmall;
ClearIsoFiles();
}
void CGameListCtrl::InitBitmaps()
{
m_imageListSmall = new wxImageList(96, 32);
SetImageList(m_imageListSmall, wxIMAGE_LIST_SMALL);
m_FlagImageIndex.resize(DiscIO::IVolume::NUMBER_OF_COUNTRIES);
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_JAPAN] = m_imageListSmall->Add(wxBitmap(Flag_Japan_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_EUROPE] = m_imageListSmall->Add(wxBitmap(Flag_Europe_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_USA] = m_imageListSmall->Add(wxBitmap(Flag_USA_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_AUSTRALIA] = m_imageListSmall->Add(wxBitmap(Flag_Australia_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_FRANCE] = m_imageListSmall->Add(wxBitmap(Flag_France_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_GERMANY] = m_imageListSmall->Add(wxBitmap(Flag_Germany_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_ITALY] = m_imageListSmall->Add(wxBitmap(Flag_Italy_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_KOREA] = m_imageListSmall->Add(wxBitmap(Flag_Korea_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_NETHERLANDS] = m_imageListSmall->Add(wxBitmap(Flag_Netherlands_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_RUSSIA] = m_imageListSmall->Add(wxBitmap(Flag_Russia_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_SPAIN] = m_imageListSmall->Add(wxBitmap(Flag_Spain_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_TAIWAN] = m_imageListSmall->Add(wxBitmap(Flag_Taiwan_xpm));
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_WORLD] = m_imageListSmall->Add(wxBitmap(Flag_Europe_xpm)); // Uses European flag as a placeholder
m_FlagImageIndex[DiscIO::IVolume::COUNTRY_UNKNOWN] = m_imageListSmall->Add(wxBitmap(Flag_Unknown_xpm));
m_PlatformImageIndex.resize(3);
m_PlatformImageIndex[0] = m_imageListSmall->Add(wxBitmap(Platform_Gamecube_xpm));
m_PlatformImageIndex[1] = m_imageListSmall->Add(wxBitmap(Platform_Wii_xpm));
m_PlatformImageIndex[2] = m_imageListSmall->Add(wxBitmap(Platform_Wad_xpm));
m_EmuStateImageIndex.resize(6);
m_EmuStateImageIndex[0] = m_imageListSmall->Add(wxBitmap(rating_0));
m_EmuStateImageIndex[1] = m_imageListSmall->Add(wxBitmap(rating_1));
m_EmuStateImageIndex[2] = m_imageListSmall->Add(wxBitmap(rating_2));
m_EmuStateImageIndex[3] = m_imageListSmall->Add(wxBitmap(rating_3));
m_EmuStateImageIndex[4] = m_imageListSmall->Add(wxBitmap(rating_4));
m_EmuStateImageIndex[5] = m_imageListSmall->Add(wxBitmap(rating_5));
}
void CGameListCtrl::BrowseForDirectory()
{
wxString dirHome;
wxGetHomeDir(&dirHome);
// browse
wxDirDialog dialog(this, _("Browse for a directory to add"), dirHome,
wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK)
{
std::string sPath(WxStrToStr(dialog.GetPath()));
std::vector<std::string>::iterator itResult = std::find(
SConfig::GetInstance().m_ISOFolder.begin(),
SConfig::GetInstance().m_ISOFolder.end(), sPath);
if (itResult == SConfig::GetInstance().m_ISOFolder.end())
{
SConfig::GetInstance().m_ISOFolder.push_back(sPath);
SConfig::GetInstance().SaveSettings();
}
Update();
}
}
void CGameListCtrl::Update()
{
int scrollPos = wxWindow::GetScrollPos(wxVERTICAL);
// Don't let the user refresh it while a game is running
if (Core::GetState() != Core::CORE_UNINITIALIZED)
return;
if (m_imageListSmall)
{
delete m_imageListSmall;
m_imageListSmall = nullptr;
}
Hide();
ScanForISOs();
ClearAll();
if (m_ISOFiles.size() != 0)
{
// Don't load bitmaps unless there are games to list
InitBitmaps();
// add columns
InsertColumn(COLUMN_DUMMY, "");
InsertColumn(COLUMN_PLATFORM, "");
InsertColumn(COLUMN_BANNER, _("Banner"));
InsertColumn(COLUMN_TITLE, _("Title"));
InsertColumn(COLUMN_MAKER, _("Maker"));
InsertColumn(COLUMN_ID, _("ID"));
InsertColumn(COLUMN_COUNTRY, "");
InsertColumn(COLUMN_SIZE, _("Size"));
InsertColumn(COLUMN_EMULATION_STATE, _("State"));
#ifdef __WXMSW__
const int platform_padding = 0;
#else
const int platform_padding = 8;
#endif
// set initial sizes for columns
SetColumnWidth(COLUMN_DUMMY,0);
SetColumnWidth(COLUMN_PLATFORM, SConfig::GetInstance().m_showSystemColumn ? 35 + platform_padding : 0);
SetColumnWidth(COLUMN_BANNER, SConfig::GetInstance().m_showBannerColumn ? 96 + platform_padding : 0);
SetColumnWidth(COLUMN_TITLE, 175 + platform_padding);
SetColumnWidth(COLUMN_MAKER, SConfig::GetInstance().m_showMakerColumn ? 150 + platform_padding : 0);
SetColumnWidth(COLUMN_ID, SConfig::GetInstance().m_showIDColumn ? 75 + platform_padding : 0);
SetColumnWidth(COLUMN_COUNTRY, SConfig::GetInstance().m_showRegionColumn ? 32 + platform_padding : 0);
SetColumnWidth(COLUMN_EMULATION_STATE, SConfig::GetInstance().m_showStateColumn ? 50 + platform_padding : 0);
// add all items
for (int i = 0; i < (int)m_ISOFiles.size(); i++)
{
InsertItemInReportView(i);
if (SConfig::GetInstance().m_ColorCompressed && m_ISOFiles[i]->IsCompressed())
SetItemTextColour(i, wxColour(0xFF0000));
}
// Sort items by Title
if (!sorted)
last_column = 0;
sorted = false;
wxListEvent event;
event.m_col = SConfig::GetInstance().m_ListSort2;
OnColumnClick(event);
event.m_col = SConfig::GetInstance().m_ListSort;
OnColumnClick(event);
sorted = true;
SetColumnWidth(COLUMN_SIZE, SConfig::GetInstance().m_showSizeColumn ? wxLIST_AUTOSIZE : 0);
}
else
{
wxString errorString;
// We just check for one hide setting to be enabled, as we may only
// have GC games for example, and hide them, so we should show the
// second message instead
if ((SConfig::GetInstance().m_ListGC &&
SConfig::GetInstance().m_ListWii &&
SConfig::GetInstance().m_ListWad) &&
(SConfig::GetInstance().m_ListJap &&
SConfig::GetInstance().m_ListUsa &&
SConfig::GetInstance().m_ListPal))
{
errorString = _("Dolphin could not find any GameCube/Wii ISOs or WADs. Double-click here to browse for files...");
}
else
{
errorString = _("Dolphin is currently set to hide all games. Double-click here to show all games...");
}
InsertColumn(0, "");
long index = InsertItem(0, errorString);
SetItemFont(index, *wxITALIC_FONT);
SetColumnWidth(0, wxLIST_AUTOSIZE);
}
if (GetSelectedISO() == nullptr)
main_frame->UpdateGUI();
Show();
AutomaticColumnWidth();
ScrollLines(scrollPos);
SetFocus();
}
static wxString NiceSizeFormat(u64 _size)
{
// Return a pretty filesize string from byte count.
// e.g. 1134278 -> "1.08 MiB"
const char* const unit_symbols[] = {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"};
// Find largest power of 2 less than _size.
// div 10 to get largest named unit less than _size
// 10 == log2(1024) (number of B in a KiB, KiB in a MiB, etc)
const u64 unit = IntLog2(std::max<u64>(_size, 1)) / 10;
const u64 unit_size = (1ull << (unit * 10));
// mul 1000 for 3 decimal places, add 5 to round up, div 10 for 2 decimal places
std::string value = std::to_string((_size * 1000 / unit_size + 5) / 10);
// Insert decimal point.
value.insert(value.size() - 2, ".");
return StrToWxStr(StringFromFormat("%s %s", value.c_str(), unit_symbols[unit]));
}
void CGameListCtrl::InsertItemInReportView(long _Index)
{
// When using wxListCtrl, there is no hope of per-column text colors.
// But for reference, here are the old colors that were used: (BGR)
// title: 0xFF0000
// company: 0x007030
int ImageIndex = -1;
GameListItem& rISOFile = *m_ISOFiles[_Index];
// Insert a first row with nothing in it, that will be used as the Index
long ItemIndex = InsertItem(_Index, wxEmptyString);
// Insert the platform's image in the first (visible) column
SetItemColumnImage(_Index, COLUMN_PLATFORM, m_PlatformImageIndex[rISOFile.GetPlatform()]);
if (rISOFile.GetBitmap().IsOk())
ImageIndex = m_imageListSmall->Add(rISOFile.GetBitmap());
// Set the game's banner in the second column
SetItemColumnImage(_Index, COLUMN_BANNER, ImageIndex);
wxString name = StrToWxStr(rISOFile.GetName());
std::ifstream titlestxt;
OpenFStream(titlestxt, File::GetUserPath(D_LOAD_IDX) + "titles.txt", std::ios::in);
if (titlestxt.is_open() && rISOFile.GetUniqueID().size() > 3)
{
while (!titlestxt.eof())
{
std::string line;
if (!std::getline(titlestxt, line) && titlestxt.eof())
break;
if (line.substr(0,rISOFile.GetUniqueID().size()) == rISOFile.GetUniqueID())
{
name = line.substr(rISOFile.GetUniqueID().size() + 3);
break;
}
}
titlestxt.close();
}
std::string title;
IniFile gameini = SCoreStartupParameter::LoadGameIni(rISOFile.GetUniqueID(), rISOFile.GetRevision());
if (gameini.GetIfExists("EmuState", "Title", &title))
name = title;
int disc_number = rISOFile.GetDiscNumber() + 1;
if (disc_number > 1 && name.Lower().find(wxString::Format("disc %i", disc_number)) == std::string::npos
&& name.Lower().find(wxString::Format("disc%i", disc_number)) == std::string::npos)
{
name = wxString::Format(_("%s (Disc %i)"), name.c_str(), disc_number);
}
SetItem(_Index, COLUMN_TITLE, name, -1);
SetItem(_Index, COLUMN_MAKER, StrToWxStr(rISOFile.GetCompany()), -1);
// Emulation state
SetItemColumnImage(_Index, COLUMN_EMULATION_STATE, m_EmuStateImageIndex[rISOFile.GetEmuState()]);
// Country
SetItemColumnImage(_Index, COLUMN_COUNTRY, m_FlagImageIndex[rISOFile.GetCountry()]);
// File size
SetItem(_Index, COLUMN_SIZE, NiceSizeFormat(rISOFile.GetFileSize()), -1);
// Game ID
SetItem(_Index, COLUMN_ID, rISOFile.GetUniqueID(), -1);
// Background color
SetBackgroundColor();
// Item data
SetItemData(_Index, ItemIndex);
}
static wxColour blend50(const wxColour& c1, const wxColour& c2)
{
unsigned char r,g,b,a;
r = c1.Red()/2 + c2.Red()/2;
g = c1.Green()/2 + c2.Green()/2;
b = c1.Blue()/2 + c2.Blue()/2;
a = c1.Alpha()/2 + c2.Alpha()/2;
return a << 24 | b << 16 | g << 8 | r;
}
void CGameListCtrl::SetBackgroundColor()
{
for (long i = 0; i < GetItemCount(); i++)
{
wxColour color = (i & 1) ?
blend50(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT),
wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)) :
wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
CGameListCtrl::SetItemBackgroundColour(i, color);
}
}
void CGameListCtrl::ScanForISOs()
{
ClearIsoFiles();
CFileSearch::XStringVector Directories(SConfig::GetInstance().m_ISOFolder);
if (SConfig::GetInstance().m_RecursiveISOFolder)
{
for (u32 i = 0; i < Directories.size(); i++)
{
File::FSTEntry FST_Temp;
File::ScanDirectoryTree(Directories[i], FST_Temp);
for (auto& Entry : FST_Temp.children)
{
if (Entry.isDirectory)
{
bool duplicate = false;
for (auto& Directory : Directories)
{
if (Directory == Entry.physicalName)
{
duplicate = true;
break;
}
}
if (!duplicate)
Directories.push_back(Entry.physicalName);
}
}
}
}
CFileSearch::XStringVector Extensions;
if (SConfig::GetInstance().m_ListGC)
Extensions.push_back("*.gcm");
if (SConfig::GetInstance().m_ListWii || SConfig::GetInstance().m_ListGC)
{
Extensions.push_back("*.iso");
Extensions.push_back("*.ciso");
Extensions.push_back("*.gcz");
Extensions.push_back("*.wbfs");
}
if (SConfig::GetInstance().m_ListWad)
Extensions.push_back("*.wad");
CFileSearch FileSearch(Extensions, Directories);
const CFileSearch::XStringVector& rFilenames = FileSearch.GetFileNames();
if (rFilenames.size() > 0)
{
wxProgressDialog dialog(
_("Scanning for ISOs"),
_("Scanning..."),
(int)rFilenames.size() - 1,
this,
wxPD_APP_MODAL |
wxPD_AUTO_HIDE |
wxPD_CAN_ABORT |
wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME |
wxPD_SMOOTH // - makes updates as small as possible (down to 1px)
);
for (u32 i = 0; i < rFilenames.size(); i++)
{
std::string FileName;
SplitPath(rFilenames[i], nullptr, &FileName, nullptr);
// Update with the progress (i) and the message
dialog.Update(i, wxString::Format(_("Scanning %s"),
StrToWxStr(FileName)));
if (dialog.WasCancelled())
break;
auto iso_file = std::make_unique<GameListItem>(rFilenames[i]);
if (iso_file->IsValid())
{
bool list = true;
switch(iso_file->GetPlatform())
{
case GameListItem::WII_DISC:
if (!SConfig::GetInstance().m_ListWii)
list = false;
break;
case GameListItem::WII_WAD:
if (!SConfig::GetInstance().m_ListWad)
list = false;
break;
default:
if (!SConfig::GetInstance().m_ListGC)
list = false;
break;
}
switch(iso_file->GetCountry())
{
case DiscIO::IVolume::COUNTRY_AUSTRALIA:
if (!SConfig::GetInstance().m_ListAustralia)
list = false;
break;
case DiscIO::IVolume::COUNTRY_EUROPE:
if (!SConfig::GetInstance().m_ListPal)
list = false;
break;
case DiscIO::IVolume::COUNTRY_FRANCE:
if (!SConfig::GetInstance().m_ListFrance)
list = false;
break;
case DiscIO::IVolume::COUNTRY_GERMANY:
if (!SConfig::GetInstance().m_ListGermany)
list = false;
break;
case DiscIO::IVolume::COUNTRY_ITALY:
if (!SConfig::GetInstance().m_ListItaly)
list = false;
break;
case DiscIO::IVolume::COUNTRY_JAPAN:
if (!SConfig::GetInstance().m_ListJap)
list = false;
break;
case DiscIO::IVolume::COUNTRY_KOREA:
if (!SConfig::GetInstance().m_ListKorea)
list = false;
break;
case DiscIO::IVolume::COUNTRY_NETHERLANDS:
if (!SConfig::GetInstance().m_ListNetherlands)
list = false;
break;
case DiscIO::IVolume::COUNTRY_RUSSIA:
if (!SConfig::GetInstance().m_ListRussia)
list = false;
break;
case DiscIO::IVolume::COUNTRY_SPAIN:
if (!SConfig::GetInstance().m_ListSpain)
list = false;
break;
case DiscIO::IVolume::COUNTRY_TAIWAN:
if (!SConfig::GetInstance().m_ListTaiwan)
list = false;
break;
case DiscIO::IVolume::COUNTRY_USA:
if (!SConfig::GetInstance().m_ListUsa)
list = false;
break;
case DiscIO::IVolume::COUNTRY_WORLD:
if (!SConfig::GetInstance().m_ListWorld)
list = false;
break;
case DiscIO::IVolume::COUNTRY_UNKNOWN:
default:
if (!SConfig::GetInstance().m_ListUnknown)
list = false;
break;
}
if (list)
m_ISOFiles.push_back(iso_file.release());
}
}
}
if (SConfig::GetInstance().m_ListDrives)
{
const std::vector<std::string> drives = cdio_get_devices();
for (const auto& drive : drives)
{
auto gli = std::make_unique<GameListItem>(drive);
if (gli->IsValid())
m_ISOFiles.push_back(gli.release());
}
}
std::sort(m_ISOFiles.begin(), m_ISOFiles.end());
}
void CGameListCtrl::OnColBeginDrag(wxListEvent& event)
{
if (event.GetColumn() != COLUMN_TITLE && event.GetColumn() != COLUMN_MAKER)
event.Veto();
}
const GameListItem* CGameListCtrl::GetISO(size_t index) const
{
if (index < m_ISOFiles.size())
return m_ISOFiles[index];
else
return nullptr;
}
static CGameListCtrl* caller;
static int wxCALLBACK wxListCompare(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData)
{
// return 1 if item1 > item2
// return -1 if item1 < item2
// return 0 for identity
const GameListItem* iso1 = caller->GetISO(item1);
const GameListItem* iso2 = caller->GetISO(item2);
return CompareGameListItems(iso1, iso2, sortData);
}
void CGameListCtrl::OnColumnClick(wxListEvent& event)
{
if (event.GetColumn() != COLUMN_BANNER)
{
int current_column = event.GetColumn();
if (sorted)
{
if (last_column == current_column)
{
last_sort = -last_sort;
}
else
{
SConfig::GetInstance().m_ListSort2 = last_sort;
last_column = current_column;
last_sort = current_column;
}
SConfig::GetInstance().m_ListSort = last_sort;
}
else
{
last_sort = current_column;
last_column = current_column;
}
caller = this;
SortItems(wxListCompare, last_sort);
}
SetBackgroundColor();
event.Skip();
}
// This is used by keyboard gamelist search
void CGameListCtrl::OnKeyPress(wxListEvent& event)
{
static int lastKey = 0, sLoop = 0;
int Loop = 0;
for (int i = 0; i < (int)m_ISOFiles.size(); i++)
{
// Easy way to get game string
wxListItem bleh;
bleh.SetId(i);
bleh.SetColumn(COLUMN_TITLE);
bleh.SetMask(wxLIST_MASK_TEXT);
GetItem(bleh);
wxString text = bleh.GetText();
if (text.MakeUpper()[0] == event.GetKeyCode())
{
if (lastKey == event.GetKeyCode() && Loop < sLoop)
{
Loop++;
if (i+1 == (int)m_ISOFiles.size())
i = -1;
continue;
}
else if (lastKey != event.GetKeyCode())
{
sLoop = 0;
}
lastKey = event.GetKeyCode();
sLoop++;
UnselectAll();
SetItemState(i, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED,
wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
EnsureVisible(i);
break;
}
// If we get past the last game in the list,
// we'll have to go back to the first one.
if (i+1 == (int)m_ISOFiles.size() && sLoop > 0 && Loop > 0)
i = -1;
}
event.Skip();
}
// This shows a little tooltip with the current Game's emulation state
void CGameListCtrl::OnMouseMotion(wxMouseEvent& event)
{
int flags;
long subitem = 0;
const long item = HitTest(event.GetPosition(), flags, &subitem);
static int lastItem = -1;
if (GetColumnCount() <= 1)
return;
if (item != wxNOT_FOUND)
{
wxRect Rect;
#ifdef __WXMSW__
if (subitem == COLUMN_EMULATION_STATE)
#else
// The subitem parameter of HitTest is only implemented for wxMSW. On
// all other platforms it will always be -1. Check the x position
// instead.
GetItemRect(item, Rect);
if (Rect.GetX() + Rect.GetWidth() - GetColumnWidth(COLUMN_EMULATION_STATE) < event.GetX())
#endif
{
if (toolTip || lastItem == item || this != FindFocus())
{
if (lastItem != item) lastItem = -1;
event.Skip();
return;
}
// Emulation status
static const char* const emuState[] = { "Broken", "Intro", "In-Game", "Playable", "Perfect" };
const GameListItem& rISO = *m_ISOFiles[GetItemData(item)];
const int emu_state = rISO.GetEmuState();
const std::string& issues = rISO.GetIssues();
// Show a tooltip containing the EmuState and the state description
if (emu_state > 0 && emu_state < 6)
{
char temp[2048];
sprintf(temp, "^ %s%s%s", emuState[emu_state - 1],
issues.size() > 0 ? " :\n" : "", issues.c_str());
toolTip = new wxEmuStateTip(this, StrToWxStr(temp), &toolTip);
}
else
{
toolTip = new wxEmuStateTip(this, _("Not Set"), &toolTip);
}
// Get item Coords
GetItemRect(item, Rect);
int mx = Rect.GetWidth();
int my = Rect.GetY();
#ifndef __WXMSW__
// For some reason the y position does not account for the header
// row, so subtract the y position of the first visible item.
GetItemRect(GetTopItem(), Rect);
my -= Rect.GetY();
#endif
// Convert to screen coordinates
ClientToScreen(&mx, &my);
toolTip->SetBoundingRect(wxRect(mx - GetColumnWidth(COLUMN_EMULATION_STATE),
my, GetColumnWidth(COLUMN_EMULATION_STATE), Rect.GetHeight()));
toolTip->SetPosition(wxPoint(mx - GetColumnWidth(COLUMN_EMULATION_STATE),
my - 5 + Rect.GetHeight()));
lastItem = item;
}
}
if (!toolTip)
lastItem = -1;
event.Skip();
}
void CGameListCtrl::OnLeftClick(wxMouseEvent& event)
{
// Focus the clicked item.
int flags;
long item = HitTest(event.GetPosition(), flags);
if ((item != wxNOT_FOUND) && (GetSelectedItemCount() == 0) &&
(!event.ControlDown()) && (!event.ShiftDown()))
{
SetItemState(item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED );
SetItemState(item, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
wxGetApp().GetCFrame()->UpdateGUI();
}
event.Skip();
}
void CGameListCtrl::OnRightClick(wxMouseEvent& event)
{
// Focus the clicked item.
int flags;
long item = HitTest(event.GetPosition(), flags);
if (item != wxNOT_FOUND)
{
if (GetItemState(item, wxLIST_STATE_SELECTED) != wxLIST_STATE_SELECTED)
{
UnselectAll();
SetItemState(item, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED );
}
SetItemState(item, wxLIST_STATE_FOCUSED, wxLIST_STATE_FOCUSED);
}
if (GetSelectedItemCount() == 1)
{
const GameListItem* selected_iso = GetSelectedISO();
if (selected_iso)
{
wxMenu popupMenu;
popupMenu.Append(IDM_PROPERTIES, _("&Properties"));
popupMenu.Append(IDM_GAME_WIKI, _("&Wiki"));
popupMenu.AppendSeparator();
if (selected_iso->GetPlatform() != GameListItem::GAMECUBE_DISC)
{
popupMenu.Append(IDM_OPEN_SAVE_FOLDER, _("Open Wii &save folder"));
popupMenu.Append(IDM_EXPORT_SAVE, _("Export Wii save (Experimental)"));
}
popupMenu.Append(IDM_OPEN_CONTAINING_FOLDER, _("Open &containing folder"));
popupMenu.AppendCheckItem(IDM_SET_DEFAULT_ISO, _("Set as &default ISO"));
// First we have to decide a starting value when we append it
if (selected_iso->GetFileName() == SConfig::GetInstance().
m_LocalCoreStartupParameter.m_strDefaultISO)
popupMenu.FindItem(IDM_SET_DEFAULT_ISO)->Check();
popupMenu.AppendSeparator();
popupMenu.Append(IDM_DELETE_ISO, _("&Delete ISO..."));
if (selected_iso->GetPlatform() != GameListItem::WII_WAD)
{
if (selected_iso->IsCompressed())
popupMenu.Append(IDM_COMPRESS_ISO, _("Decompress ISO..."));
else if (selected_iso->GetFileName().substr(selected_iso->GetFileName().find_last_of(".")) != ".ciso" &&
selected_iso->GetFileName().substr(selected_iso->GetFileName().find_last_of(".")) != ".wbfs")
popupMenu.Append(IDM_COMPRESS_ISO, _("Compress ISO..."));
}
else
{
popupMenu.Append(IDM_LIST_INSTALL_WAD, _("Install to Wii Menu"));
}
if (selected_iso->GetPlatform() == GameListItem::GAMECUBE_DISC ||
selected_iso->GetPlatform() == GameListItem::WII_DISC)
{
wxMenuItem* changeDiscItem = popupMenu.Append(IDM_LIST_CHANGE_DISC, _("Change &Disc"));
changeDiscItem->Enable(Core::IsRunning());
}
PopupMenu(&popupMenu);
}
}
else if (GetSelectedItemCount() > 1)
{
wxMenu popupMenu;
popupMenu.Append(IDM_DELETE_ISO, _("&Delete selected ISOs..."));
popupMenu.AppendSeparator();
popupMenu.Append(IDM_MULTI_COMPRESS_ISO, _("Compress selected ISOs..."));
popupMenu.Append(IDM_MULTI_DECOMPRESS_ISO, _("Decompress selected ISOs..."));
PopupMenu(&popupMenu);
}
}
const GameListItem * CGameListCtrl::GetSelectedISO()
{
if (m_ISOFiles.size() == 0)
{
return nullptr;
}
else if (GetSelectedItemCount() == 0)
{
return nullptr;
}
else
{
long item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
if (item == wxNOT_FOUND)
{
return nullptr;
}
else
{
// Here is a little workaround for multiselections:
// when > 1 item is selected, return info on the first one
// and deselect it so the next time GetSelectedISO() is called,
// the next item's info is returned
if (GetSelectedItemCount() > 1)
SetItemState(item, 0, wxLIST_STATE_SELECTED);
return m_ISOFiles[GetItemData(item)];
}
}
}
void CGameListCtrl::OnOpenContainingFolder(wxCommandEvent& WXUNUSED (event))
{
const GameListItem* iso = GetSelectedISO();
if (!iso)
return;
wxFileName path = wxFileName::FileName(StrToWxStr(iso->GetFileName()));
path.MakeAbsolute();
WxUtils::Explore(WxStrToStr(path.GetPath()));
}
void CGameListCtrl::OnOpenSaveFolder(wxCommandEvent& WXUNUSED (event))
{
const GameListItem* iso = GetSelectedISO();
if (!iso)
return;
std::string path = iso->GetWiiFSPath();
if (!path.empty())
WxUtils::Explore(path);
}
void CGameListCtrl::OnExportSave(wxCommandEvent& WXUNUSED (event))
{
const GameListItem* iso = GetSelectedISO();
if (!iso)
return;
u64 title;
std::unique_ptr<DiscIO::IVolume> volume(DiscIO::CreateVolumeFromFilename(iso->GetFileName()));
if (volume && volume->GetTitleID((u8*)&title))
{
title = Common::swap64(title);
CWiiSaveCrypted::ExportWiiSave(title);
}
}
// Save this file as the default file
void CGameListCtrl::OnSetDefaultISO(wxCommandEvent& event)
{
const GameListItem* iso = GetSelectedISO();
if (!iso) return;
if (event.IsChecked())
{
// Write the new default value and save it the ini file
SConfig::GetInstance().m_LocalCoreStartupParameter.m_strDefaultISO =
iso->GetFileName();
SConfig::GetInstance().SaveSettings();
}
else
{
// Otherwise blank the value and save it
SConfig::GetInstance().m_LocalCoreStartupParameter.m_strDefaultISO = "";
SConfig::GetInstance().SaveSettings();
}
}
void CGameListCtrl::OnDeleteISO(wxCommandEvent& WXUNUSED (event))
{
if (GetSelectedItemCount() == 1)
{
const GameListItem* iso = GetSelectedISO();
if (!iso)
return;
if (wxMessageBox(_("Are you sure you want to delete this file? It will be gone forever!"),
wxMessageBoxCaptionStr, wxYES_NO | wxICON_EXCLAMATION) == wxYES)
{
File::Delete(iso->GetFileName());
Update();
}
}
else
{
if (wxMessageBox(_("Are you sure you want to delete these files?\nThey will be gone forever!"),
wxMessageBoxCaptionStr, wxYES_NO | wxICON_EXCLAMATION) == wxYES)
{
int selected = GetSelectedItemCount();
for (int i = 0; i < selected; i++)
{
const GameListItem* iso = GetSelectedISO();
File::Delete(iso->GetFileName());
}
Update();
}
}
}
void CGameListCtrl::OnProperties(wxCommandEvent& WXUNUSED (event))
{
const GameListItem* iso = GetSelectedISO();
if (!iso)
return;
CISOProperties* ISOProperties = new CISOProperties(iso->GetFileName(), this);
ISOProperties->Show();
}
void CGameListCtrl::OnWiki(wxCommandEvent& WXUNUSED (event))
{
const GameListItem* iso = GetSelectedISO();
if (!iso)
return;
std::string wikiUrl = "https://wiki.dolphin-emu.org/dolphin-redirect.php?gameid=" + iso->GetUniqueID();
WxUtils::Launch(wikiUrl);
}
bool CGameListCtrl::MultiCompressCB(const std::string& text, float percent, void* arg)
{
percent = (((float)m_currentItem) + percent) / (float)m_numberItem;
wxString textString(StrToWxStr(StringFromFormat("%s (%i/%i) - %s",
m_currentFilename.c_str(), (int)m_currentItem + 1,
(int)m_numberItem, text.c_str())));
return ((wxProgressDialog*)arg)->Update((int)(percent * 1000), textString);
}
void CGameListCtrl::OnMultiCompressISO(wxCommandEvent& /*event*/)
{
CompressSelection(true);
}
void CGameListCtrl::OnMultiDecompressISO(wxCommandEvent& /*event*/)
{
CompressSelection(false);
}
void CGameListCtrl::CompressSelection(bool _compress)
{
wxString dirHome;
wxGetHomeDir(&dirHome);
wxDirDialog browseDialog(this, _("Browse for output directory"), dirHome,
wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
if (browseDialog.ShowModal() != wxID_OK)
return;
bool all_good = true;
{
wxProgressDialog progressDialog(
_compress ? _("Compressing ISO") : _("Decompressing ISO"),
_("Working..."),
1000,
this,
wxPD_APP_MODAL |
wxPD_CAN_ABORT |
wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME |
wxPD_SMOOTH
);
m_currentItem = 0;
m_numberItem = GetSelectedItemCount();
for (u32 i = 0; i < m_numberItem; i++)
{
const GameListItem* iso = GetSelectedISO();
if (iso->GetPlatform() == GameListItem::WII_WAD || iso->GetFileName().rfind(".wbfs") != std::string::npos)
continue;
if (!iso->IsCompressed() && _compress)
{
std::string FileName, FileExt;
SplitPath(iso->GetFileName(), nullptr, &FileName, &FileExt);
m_currentFilename = FileName;
FileName.append(".gcz");
std::string OutputFileName;
BuildCompleteFilename(OutputFileName,
WxStrToStr(browseDialog.GetPath()),
FileName);
if (File::Exists(OutputFileName) &&
wxMessageBox(
wxString::Format(_("The file %s already exists.\nDo you wish to replace it?"),
StrToWxStr(OutputFileName)),
_("Confirm File Overwrite"),
wxYES_NO) == wxNO)
continue;
all_good &= DiscIO::CompressFileToBlob(iso->GetFileName(),
OutputFileName,
(iso->GetPlatform() == GameListItem::WII_DISC) ? 1 : 0,
16384, &MultiCompressCB, &progressDialog);
}
else if (iso->IsCompressed() && !_compress)
{
std::string FileName, FileExt;
SplitPath(iso->GetFileName(), nullptr, &FileName, &FileExt);
m_currentFilename = FileName;
if (iso->GetPlatform() == GameListItem::WII_DISC)
FileName.append(".iso");
else
FileName.append(".gcm");
std::string OutputFileName;
BuildCompleteFilename(OutputFileName,
WxStrToStr(browseDialog.GetPath()),
FileName);
if (File::Exists(OutputFileName) &&
wxMessageBox(
wxString::Format(_("The file %s already exists.\nDo you wish to replace it?"),
StrToWxStr(OutputFileName)),
_("Confirm File Overwrite"),
wxYES_NO) == wxNO)
continue;
all_good &= DiscIO::DecompressBlobToFile(iso->GetFileName().c_str(),
OutputFileName.c_str(), &MultiCompressCB, &progressDialog);
}
m_currentItem++;
}
}
if (!all_good)
WxUtils::ShowErrorDialog(_("Dolphin was unable to complete the requested action."));
Update();
}
bool CGameListCtrl::CompressCB(const std::string& text, float percent, void* arg)
{
return ((wxProgressDialog*)arg)->
Update((int)(percent * 1000), StrToWxStr(text));
}
void CGameListCtrl::OnCompressISO(wxCommandEvent& WXUNUSED (event))
{
const GameListItem* iso = GetSelectedISO();
if (!iso)
return;
wxString path;
std::string FileName, FilePath, FileExtension;
SplitPath(iso->GetFileName(), &FilePath, &FileName, &FileExtension);
do
{
if (iso->IsCompressed())
{
wxString FileType;
if (iso->GetPlatform() == GameListItem::WII_DISC)
FileType = _("All Wii ISO files (iso)") + "|*.iso";
else
FileType = _("All GameCube GCM files (gcm)") + "|*.gcm";
path = wxFileSelector(
_("Save decompressed GCM/ISO"),
StrToWxStr(FilePath),
StrToWxStr(FileName) + FileType.After('*'),
wxEmptyString,
FileType + "|" + wxGetTranslation(wxALL_FILES),
wxFD_SAVE,
this);
}
else
{
path = wxFileSelector(
_("Save compressed GCM/ISO"),
StrToWxStr(FilePath),
StrToWxStr(FileName) + ".gcz",
wxEmptyString,
_("All compressed GC/Wii ISO files (gcz)") +
wxString::Format("|*.gcz|%s", wxGetTranslation(wxALL_FILES)),
wxFD_SAVE,
this);
}
if (!path)
return;
} while (wxFileExists(path) &&
wxMessageBox(
wxString::Format(_("The file %s already exists.\nDo you wish to replace it?"), path.c_str()),
_("Confirm File Overwrite"),
wxYES_NO) == wxNO);
bool all_good = false;
{
wxProgressDialog dialog(
iso->IsCompressed() ? _("Decompressing ISO") : _("Compressing ISO"),
_("Working..."),
1000,
this,
wxPD_APP_MODAL |
wxPD_CAN_ABORT |
wxPD_ELAPSED_TIME | wxPD_ESTIMATED_TIME | wxPD_REMAINING_TIME |
wxPD_SMOOTH
);
if (iso->IsCompressed())
all_good = DiscIO::DecompressBlobToFile(iso->GetFileName(),
WxStrToStr(path), &CompressCB, &dialog);
else
all_good = DiscIO::CompressFileToBlob(iso->GetFileName(),
WxStrToStr(path),
(iso->GetPlatform() == GameListItem::WII_DISC) ? 1 : 0,
16384, &CompressCB, &dialog);
}
if (!all_good)
WxUtils::ShowErrorDialog(_("Dolphin was unable to complete the requested action."));
Update();
}
void CGameListCtrl::OnChangeDisc(wxCommandEvent& WXUNUSED(event))
{
const GameListItem* iso = GetSelectedISO();
if (!iso || !Core::IsRunning())
return;
DVDInterface::ChangeDisc(WxStrToStr(iso->GetFileName()));
}
void CGameListCtrl::OnSize(wxSizeEvent& event)
{
if (lastpos == event.GetSize())
return;
lastpos = event.GetSize();
AutomaticColumnWidth();
event.Skip();
}
void CGameListCtrl::AutomaticColumnWidth()
{
wxRect rc(GetClientRect());
if (GetColumnCount() == 1)
{
SetColumnWidth(0, rc.GetWidth());
}
else if (GetColumnCount() > 0)
{
int resizable = rc.GetWidth() - (
GetColumnWidth(COLUMN_PLATFORM)
+ GetColumnWidth(COLUMN_BANNER)
+ GetColumnWidth(COLUMN_ID)
+ GetColumnWidth(COLUMN_COUNTRY)
+ GetColumnWidth(COLUMN_SIZE)
+ GetColumnWidth(COLUMN_EMULATION_STATE));
// We hide the Maker column if the window is too small
if (resizable > 400)
{
if (SConfig::GetInstance().m_showMakerColumn)
{
SetColumnWidth(COLUMN_TITLE, resizable / 2);
SetColumnWidth(COLUMN_MAKER, resizable / 2);
}
else
{
SetColumnWidth(COLUMN_TITLE, resizable);
}
}
else
{
SetColumnWidth(COLUMN_TITLE, resizable);
SetColumnWidth(COLUMN_MAKER, 0);
}
}
}
void CGameListCtrl::UnselectAll()
{
for (int i = 0; i < GetItemCount(); i++)
{
SetItemState(i, 0, wxLIST_STATE_SELECTED);
}
}