/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2002-2010 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "PrecompiledHeader.h" #include "AppCoreThread.h" #include "System.h" #include "MemoryCardFile.h" #include "ConfigurationPanels.h" #include "MemoryCardPanels.h" #include "Dialogs/ConfigurationDialog.h" #include "Utilities/IniInterface.h" #include "../../Sio.h" #include #include #include using namespace pxSizerFlags; using namespace Panels; static bool IsMcdFormatted( wxFFile& fhand ) { static const char formatted_string[] = "Sony PS2 Memory Card Format"; static const int fmtstrlen = sizeof( formatted_string )-1; char dest[fmtstrlen]; fhand.Read( dest, fmtstrlen ); return memcmp( formatted_string, dest, fmtstrlen ) == 0; } //sets IsPresent if the file is valid, and derived properties (filename, formatted, size, etc) bool EnumerateMemoryCard( McdSlotItem& dest, const wxFileName& filename, const wxDirName basePath ) { dest.IsFormatted = false; dest.IsPresent = false; const wxString fullpath( filename.GetFullPath() ); if( !filename.FileExists() ) return false; //DevCon.WriteLn( fullpath ); wxFFile mcdFile( fullpath ); if( !mcdFile.IsOpened() ) return false; // wx should log the error for us. if( mcdFile.Length() < (1024*528) ) { Console.Warning( "... MemoryCard appears to be truncated. Ignoring." ); return false; } dest.IsPresent = true; dest.Filename = filename; if( filename.GetFullPath() == (basePath+filename.GetFullName()).GetFullPath() ) dest.Filename = filename.GetFullName(); dest.SizeInMB = (uint)(mcdFile.Length() / (1024 * 528 * 2)); dest.IsFormatted = IsMcdFormatted( mcdFile ); filename.GetTimes( NULL, &dest.DateModified, &dest.DateCreated ); return true; } //avih: unused /* static int EnumerateMemoryCards( McdList& dest, const wxArrayString& files ) { int pushed = 0; Console.WriteLn( Color_StrongBlue, "Enumerating memory cards..." ); for( size_t i=0; i 0 ) Console.WriteLn( Color_StrongBlue, "Memory card Enumeration Complete." ); else Console.WriteLn( Color_StrongBlue, "No valid memory card found." ); return pushed; } */ // -------------------------------------------------------------------------------------- // McdListItem (implementations) // -------------------------------------------------------------------------------------- bool McdSlotItem::IsMultitapSlot() const { return FileMcd_IsMultitapSlot(Slot); } uint McdSlotItem::GetMtapPort() const { return FileMcd_GetMtapPort(Slot); } uint McdSlotItem::GetMtapSlot() const { return FileMcd_GetMtapSlot(Slot); } // Compares two cards -- If this equality comparison is used on items where // no filename is specified, then the check will include port and slot. bool McdSlotItem::operator==( const McdSlotItem& right ) const { bool fileEqu; if( Filename.GetFullName().IsEmpty() ) fileEqu = OpEqu(Slot); else fileEqu = OpEqu(Filename); return fileEqu && OpEqu(IsPresent) && OpEqu(IsEnabled) && OpEqu(SizeInMB) && OpEqu(IsFormatted) && OpEqu(DateCreated) && OpEqu(DateModified); } bool McdSlotItem::operator!=( const McdSlotItem& right ) const { return operator==( right ); } //DEFINE_EVENT_TYPE( pxEvt_RefreshSelections ); // ===================================================================================================== // BaseMcdListPanel (implementations) // ===================================================================================================== Panels::BaseMcdListPanel::BaseMcdListPanel( wxWindow* parent ) : _parent( parent ) { m_FolderPicker = new DirPickerPanel( this, FolderId_MemoryCards, //_("memory card Search Path:"), // static box label _("Select folder with PS2 memory cards") // dir picker popup label ); m_listview = NULL; m_btn_Refresh = new wxButton( this, wxID_ANY, _("Refresh list") ); Connect( m_btn_Refresh->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(BaseMcdListPanel::OnRefreshSelections) ); //Connect( pxEvt_RefreshSelections, wxCommandEventHandler(BaseMcdListPanel::OnRefreshSelections) ); } void Panels::BaseMcdListPanel::RefreshMcds() const { wxCommandEvent refit( wxEVT_COMMAND_BUTTON_CLICKED ); refit.SetId( m_btn_Refresh->GetId() ); GetEventHandler()->AddPendingEvent( refit ); } void Panels::BaseMcdListPanel::CreateLayout() { //if( m_listview ) m_listview->SetMinSize( wxSize( 480, 140 ) ); wxFlexGridSizer* s_flex=new wxFlexGridSizer(3,1, 0, 0); s_flex->AddGrowableCol(0); s_flex->AddGrowableRow(1); SetSizer(s_flex); wxBoxSizer& s_buttons(*new wxBoxSizer( wxHORIZONTAL )); s_leftside_buttons = new wxBoxSizer( wxHORIZONTAL ); s_rightside_buttons = new wxBoxSizer( wxHORIZONTAL ); s_buttons += s_leftside_buttons | pxAlignLeft; s_buttons += pxStretchSpacer(); s_buttons += s_rightside_buttons | pxAlignRight; if( m_FolderPicker ) *this += m_FolderPicker | pxExpand; else *this += StdPadding;//we need the 'else' because we need these items to land into the proper rows of s_flex. if( m_listview ) *this += m_listview | pxExpand; else *this += StdPadding; *this += s_buttons | pxExpand; *s_leftside_buttons += m_btn_Refresh; if (m_listview) { IniLoader loader; ScopedIniGroup group( loader, L"MemoryCardListPanel" ); m_listview->LoadSaveColumns( loader ); } } void Panels::BaseMcdListPanel::Apply() { // Save column widths to the configuration file. Since these are used *only* by this // dialog, we use a direct local ini save approach, instead of going through g_conf. if (m_listview) { IniSaver saver; ScopedIniGroup group( saver, L"MemoryCardListPanel" ); m_listview->LoadSaveColumns(saver); } } void Panels::BaseMcdListPanel::AppStatusEvent_OnSettingsApplied() { if( (m_MultitapEnabled[0] != g_Conf->EmuOptions.MultitapPort0_Enabled) || (m_MultitapEnabled[1] != g_Conf->EmuOptions.MultitapPort1_Enabled) ) { m_MultitapEnabled[0] = g_Conf->EmuOptions.MultitapPort0_Enabled; m_MultitapEnabled[1] = g_Conf->EmuOptions.MultitapPort1_Enabled; RefreshMcds(); } } // -------------------------------------------------------------------------------------- // McdDataObject // -------------------------------------------------------------------------------------- class WXDLLEXPORT McdDataObject : public wxDataObjectSimple { DECLARE_NO_COPY_CLASS(McdDataObject) protected: int m_viewIndex; public: McdDataObject(int viewIndex = -1) : wxDataObjectSimple( wxDF_PRIVATE ) { m_viewIndex = viewIndex; } uint GetViewIndex() const { pxAssertDev( m_viewIndex >= 0, "memory card view-Index is uninitialized (invalid drag&drop object state)" ); return (uint)m_viewIndex; } size_t GetDataSize() const { return sizeof(u32); } bool GetDataHere(void *buf) const { *(u32*)buf = GetViewIndex(); return true; } virtual bool SetData(size_t len, const void *buf) { if( !pxAssertDev( len == sizeof(u32), "Data length mismatch on memory card drag&drop operation." ) ) return false; m_viewIndex = *(u32*)buf; return true;//( (uint)m_viewIndex < 8 ); // sanity check (unsigned, so that -1 also is invalid) :) } // Must provide overloads to avoid hiding them (and warnings about it) virtual size_t GetDataSize(const wxDataFormat&) const { return GetDataSize(); } virtual bool GetDataHere(const wxDataFormat&, void *buf) const { return GetDataHere(buf); } virtual bool SetData(const wxDataFormat&, size_t len, const void *buf) { return SetData(len, buf); } }; class McdDropTarget : public wxDropTarget { protected: BaseMcdListView* m_listview; public: McdDropTarget( BaseMcdListView* listview=NULL ) { m_listview = listview; SetDataObject(new McdDataObject()); } // these functions are called when data is moved over position (x, y) and // may return either wxDragCopy, wxDragMove or wxDragNone depending on // what would happen if the data were dropped here. // // the last parameter is what would happen by default and is determined by // the platform-specific logic (for example, under Windows it's wxDragCopy // if Ctrl key is pressed and wxDragMove otherwise) except that it will // always be wxDragNone if the carried data is in an unsupported format. // called when the mouse moves in the window - shouldn't take long to // execute or otherwise mouse movement would be too slow. virtual wxDragResult OnDragOver(wxCoord x, wxCoord y, wxDragResult def) { int flags = 0; int viewIndex = m_listview->HitTest( wxPoint(x,y), flags); m_listview->SetTargetedItem( viewIndex ); // can always drop. non item target is the filesystem placeholder. //if( wxNOT_FOUND == viewIndex ) return wxDragNone; return def; } virtual void OnLeave() { m_listview->SetTargetedItem( wxNOT_FOUND ); } // this function is called when data is dropped at position (x, y) - if it // returns true, OnData() will be called immediately afterwards which will // allow to retrieve the data dropped. virtual bool OnDrop(wxCoord x, wxCoord y) { int flags = 0; int viewIndex = m_listview->HitTest( wxPoint(x,y), flags); return true;// can always drop. non item target is the filesystem placeholder.//( wxNOT_FOUND != viewIndex ); } // may be called *only* from inside OnData() and will fill m_dataObject // with the data from the drop source if it returns true virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def) { m_listview->SetTargetedItem( wxNOT_FOUND ); int flags = 0; int destViewIndex = m_listview->HitTest( wxPoint(x,y), flags); if( wxNOT_FOUND == destViewIndex ) destViewIndex=-1;//non list item target is the filesystem placeholder. if ( !GetData() ) return wxDragNone; McdDataObject *dobj = (McdDataObject *)m_dataObject; int sourceViewIndex = dobj->GetViewIndex(); wxDragResult result = OnDropMcd( m_listview->GetMcdProvider().GetCardForViewIndex( sourceViewIndex ), m_listview->GetMcdProvider().GetCardForViewIndex( destViewIndex ), def ); if( wxDragNone == result ) return wxDragNone; m_listview->GetMcdProvider().RefreshMcds(); return result; } virtual wxDragResult OnDropMcd( McdSlotItem& src, McdSlotItem& dest, wxDragResult def ) { if( src.Slot == dest.Slot ) return wxDragNone; //if( !pxAssert( (src.Slot >= 0) && (dest.Slot >= 0) ) ) return wxDragNone; const wxDirName basepath( m_listview->GetMcdProvider().GetMcdPath() ); bool result = true; if( wxDragCopy == def ) { if( !m_listview->GetMcdProvider().UiDuplicateCard(src, dest) ) return wxDragNone; } else if( wxDragMove == def ) { // source can only be an existing card. // if dest is a ps2-port (empty or not) -> swap cards between ports. // is dest is a non-ps2-port -> remove card from port. // Note: For the sake of usability, automatically enable dest if a ps2-port. if (src.IsPresent) { wxFileName tmpFilename = dest.Filename; bool tmpPresent = dest.IsPresent; if (src.Slot<0 && m_listview->GetMcdProvider().isFileAssignedToInternalSlot(src.Filename)) m_listview->GetMcdProvider().RemoveCardFromSlot(src.Filename); dest.Filename = src.Filename; dest.IsEnabled = dest.IsPresent? dest.IsEnabled:true; dest.IsPresent = src.IsPresent; if (dest.Slot>=0) {//2 internal slots: swap src.Filename = tmpFilename; src.IsPresent = tmpPresent; } else {//dest is at the filesystem (= remove card from slot) src.Filename = L""; src.IsPresent = false; src.IsEnabled = false; } } } return def; } }; enum McdMenuId { McdMenuId_Create = 0x888, //McdMenuId_Mount, McdMenuId_Rename, McdMenuId_RefreshList, McdMenuId_AssignUnassign, McdMenuId_Duplicate, }; Panels::MemoryCardListPanel_Simple* g_uglyPanel=NULL; void g_uglyFunc(){if (g_uglyPanel) g_uglyPanel->OnChangedListSelection();} Panels::MemoryCardListPanel_Simple::~MemoryCardListPanel_Simple() throw(){g_uglyPanel=NULL;} Panels::MemoryCardListPanel_Simple::MemoryCardListPanel_Simple( wxWindow* parent ) : _parent( parent ) { m_MultitapEnabled[0] = false; m_MultitapEnabled[1] = false; m_listview = new MemoryCardListView_Simple(this); m_listview->SetMinSize(wxSize(620, m_listview->GetCharHeight() * 13)); // 740 is nice for default font sizes m_listview->SetDropTarget( new McdDropTarget(m_listview) ); //m_button_Mount = new wxButton(this, wxID_ANY, _("Enable port")); m_button_AssignUnassign = new wxButton(this, wxID_ANY, _("Eject")); m_button_Duplicate = new wxButton(this, wxID_ANY, _("Duplicate ...")); m_button_Rename = new wxButton(this, wxID_ANY, _("Rename ...")); m_button_Create = new wxButton(this, wxID_ANY, _("Create ...")); // ------------------------------------ // Sizer / Layout Section // ------------------------------------ CreateLayout(); *s_leftside_buttons += 20; //*s_leftside_buttons += m_button_Mount; //*s_leftside_buttons += 20; *s_leftside_buttons += Label(_("Card: ")) | pxMiddle; *s_leftside_buttons += m_button_AssignUnassign; *s_leftside_buttons += 20; *s_leftside_buttons += m_button_Duplicate; *s_leftside_buttons += 2; *s_leftside_buttons += m_button_Rename; *s_leftside_buttons += 2; *s_leftside_buttons += m_button_Create; SetSizerAndFit(GetSizer()); parent->SetWindowStyle(parent->GetWindowStyle() | wxRESIZE_BORDER); Connect( m_listview->GetId(), wxEVT_COMMAND_LIST_BEGIN_DRAG, wxListEventHandler(MemoryCardListPanel_Simple::OnListDrag)); Connect( m_listview->GetId(), wxEVT_COMMAND_LIST_ITEM_SELECTED, wxListEventHandler(MemoryCardListPanel_Simple::OnListSelectionChanged)); Connect( m_listview->GetId(), wxEVT_COMMAND_LIST_ITEM_ACTIVATED, wxListEventHandler(MemoryCardListPanel_Simple::OnItemActivated));//enter or double click //Deselected is not working for some reason (e.g. when clicking an empty row at the table?) - avih Connect( m_listview->GetId(), wxEVT_COMMAND_LIST_ITEM_DESELECTED, wxListEventHandler(MemoryCardListPanel_Simple::OnListSelectionChanged)); Connect( m_listview->GetId(), wxEVT_COMMAND_LIST_ITEM_RIGHT_CLICK, wxListEventHandler(MemoryCardListPanel_Simple::OnOpenItemContextMenu) ); // Connect( m_button_Mount->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnMountCard)); Connect( m_button_Create->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnCreateOrDeleteCard)); Connect( m_button_Rename->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnRenameFile)); Connect( m_button_Duplicate->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnDuplicateFile)); Connect( m_button_AssignUnassign->GetId(), wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnAssignUnassignFile)); // Popup Menu Connections! Connect( McdMenuId_Create, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnCreateOrDeleteCard) ); //Connect( McdMenuId_Mount, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnMountCard) ); Connect( McdMenuId_Rename, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnRenameFile) ); Connect( McdMenuId_AssignUnassign, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnAssignUnassignFile) ); Connect( McdMenuId_Duplicate, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnDuplicateFile) ); Connect( McdMenuId_RefreshList, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MemoryCardListPanel_Simple::OnRefreshSelections) ); //because the wxEVT_COMMAND_LIST_ITEM_DESELECTED doesn't work (buttons stay enabled when clicking an empty area of the list), // m_listview can send us an event that indicates a change at the list. Ugly, but works. g_uglyPanel=this; m_listview->setExternHandler(g_uglyFunc); } void Panels::MemoryCardListPanel_Simple::UpdateUI() { if( !m_listview ) return; int sel = m_listview->GetFirstSelected(); m_button_Create->Enable(); if( wxNOT_FOUND == sel ) { m_button_Create->SetLabel( _("Create ...") ); pxSetToolTip( m_button_Create, _("Create a new memory card." )); // m_button_Mount->Disable(); m_button_Rename->Disable(); m_button_Duplicate->Disable(); m_button_AssignUnassign->Disable(); return; } const McdSlotItem& card( GetCardForViewIndex(sel) ); m_button_Rename->Enable( card.IsPresent ); wxString renameTip = _("Rename this memory card ..."); pxSetToolTip( m_button_Rename, renameTip ); m_button_AssignUnassign->Enable( card.IsPresent ); m_button_AssignUnassign->SetLabel( card.Slot>=0 ? _("Eject") : _("Insert ...") ); wxString assignTip = (card.Slot>=0)?_("Eject the card from this port"):_("Insert this card to a port ..."); pxSetToolTip( m_button_AssignUnassign, assignTip ); m_button_Duplicate->Enable( card.IsPresent ); wxString dupTip = _("Create a duplicate of this memory card ..."); pxSetToolTip( m_button_Duplicate, dupTip ); //m_button_Create->Enable( card.Slot>=0 || card.IsPresent); m_button_Create->SetLabel( card.IsPresent ? _("Delete") : _("Create ...") ); if (card.IsPresent) pxSetToolTip( m_button_Create, _("Permanently delete this memory card from disk (all contents are lost)")); else if( card.Slot >= 0 ) pxSetToolTip( m_button_Create, _("Create a new memory card and assign it to this Port." )); else pxSetToolTip( m_button_Create, _("Create a new memory card." )); /* m_button_Mount->Enable( card.IsPresent && card.Slot>=0); m_button_Mount->SetLabel( card.IsEnabled ? _("Disable Port") : _("Enable Port") ); pxSetToolTip( m_button_Mount, card.IsEnabled ? _("Disable the selected PS2-Port (this memory card will be invisible to games/BIOS).") : _("Enable the selected PS2-Port (games/BIOS will see this memory card).") ); */ } void Panels::MemoryCardListPanel_Simple::Apply() { _parent::Apply(); ScopedCoreThreadClose closed_core; closed_core.AllowResume(); int used=0; Console.WriteLn( L"Apply Memory cards:" ); for( uint slot=0; slot<8; ++slot ) { g_Conf->Mcd[slot].Enabled = m_Cards[slot].IsEnabled && m_Cards[slot].IsPresent; if (m_Cards[slot].IsPresent) g_Conf->Mcd[slot].Filename = m_Cards[slot].Filename; else g_Conf->Mcd[slot].Filename = L""; if( g_Conf->Mcd[slot].Enabled ) { used++; Console.WriteLn( L"slot[%d]='%s'", slot, g_Conf->Mcd[slot].Filename.GetFullName().c_str() ); } } if( !used ) Console.WriteLn( L"No active slots" ); SetForceMcdEjectTimeoutNow(); } void Panels::MemoryCardListPanel_Simple::AppStatusEvent_OnSettingsApplied() { for( uint slot=0; slot<8; ++slot ) { m_Cards[slot].IsEnabled = g_Conf->Mcd[slot].Enabled; m_Cards[slot].Filename = g_Conf->Mcd[slot].Filename; //automatically create the enabled but non-existing file such that it can be managed (else will get created anyway on boot) wxString targetFile = (GetMcdPath() + m_Cards[slot].Filename.GetFullName()).GetFullPath(); if ( m_Cards[slot].IsEnabled && !wxFileExists( targetFile ) ) { wxString errMsg; if (isValidNewFilename(m_Cards[slot].Filename.GetFullName(), GetMcdPath(), errMsg, 5)) { if ( !Dialogs::CreateMemoryCardDialog::CreateIt(targetFile, 8) ) Console.Error( L"Automatic createion of MCD '%s' failed. Hope for the best...", targetFile.c_str() ); else Console.WriteLn( L"memcard created: '%s'.", targetFile.c_str() ); } else { Console.Error( L"memcard was enabled but had an invalid file name. Aborting automatic creation. Hope for the best... (%s)", errMsg.c_str() ); } } if ( !m_Cards[slot].IsEnabled || !wxFileExists( targetFile ) ) { m_Cards[slot].IsEnabled = false; m_Cards[slot].IsPresent = false; m_Cards[slot].Filename = L""; } } DoRefresh(); _parent::AppStatusEvent_OnSettingsApplied(); } //BUG: the next function is never reached because, for some reason, IsoDropTarget::OnDropFiles is called instead. // Interestingly, IsoDropTarget::OnDropFiles actually "detects" a memory card file as a valid Audio-CD ISO... - avih bool Panels::MemoryCardListPanel_Simple::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) { if( filenames.GetCount() == 1 && wxFileName(filenames[0]).IsDir() ) { m_FolderPicker->SetPath( filenames[0] ); return true; } return false; } bool Panels::MemoryCardListPanel_Simple::ValidateEnumerationStatus() { if( m_listview ) m_listview->SetMcdProvider( NULL ); return false; } void Panels::MemoryCardListPanel_Simple::DoRefresh() { for( uint slot=0; slot<8; ++slot ) { //if( FileMcd_IsMultitapSlot(slot) && !m_MultitapEnabled[FileMcd_GetMtapPort(slot)] ) // continue; //wxFileName fullpath( m_FolderPicker->GetPath() + g_Conf->Mcd[slot].Filename.GetFullName() ); wxFileName fullpath = m_FolderPicker->GetPath() + m_Cards[slot].Filename.GetFullName(); EnumerateMemoryCard( m_Cards[slot], fullpath, m_FolderPicker->GetPath()); m_Cards[slot].Slot = slot; } ReadFilesAtMcdFolder(); if( m_listview ) m_listview->SetMcdProvider( this ); UpdateUI(); } // ===================================================================================================== // MemoryCardListPanel_Simple (implementations) // ===================================================================================================== void Panels::MemoryCardListPanel_Simple::UiCreateNewCard( McdSlotItem& card ) {//card can also be the filesystem placeholder. On that case, the changes // made to it will be reverted on refresh, and we'll just have a new card at the folder. if( card.IsPresent ){ Console.WriteLn("Error: Aborted: create mcd invoked but but a file is already associated."); return; } ScopedCoreThreadClose closed_core; Dialogs::CreateMemoryCardDialog dialog( this, m_FolderPicker->GetPath(), L"my memory card" ); wxWindowID result = dialog.ShowModal(); if (result != wxID_CANCEL) { card.IsEnabled = true; card.Filename = dialog.result_createdMcdFilename; card.IsPresent = true; if ( card.Slot >= 0) Console.WriteLn(L"setting new card to slot %u: '%s'", card.Slot, card.Filename.GetFullName().c_str()); else Console.WriteLn(L"Created a new unassigned card file: '%s'", card.Filename.GetFullName().c_str() ); } else card.IsEnabled=false; Apply(); RefreshSelections(); closed_core.AllowResume(); } void Panels::MemoryCardListPanel_Simple::UiDeleteCard( McdSlotItem& card ) { if( !card.IsPresent ){ Console.WriteLn("Error: Aborted: delete mcd invoked but but a file is not associated."); return; } bool result = true; if( card.IsFormatted ) { wxString content; content.Printf( pxE( "!Notice:Mcd:Delete", L"You are about to delete the formatted memory card '%s'. " L"All data on this card will be lost! Are you absolutely and quite positively sure?" ), card.Filename.GetFullName().c_str() ); result = Msgbox::YesNo( content, _("Delete memory file?") ); } if( result ) { ScopedCoreThreadClose closed_core; wxFileName fullpath( m_FolderPicker->GetPath() + card.Filename.GetFullName()); card.IsEnabled=false; Apply(); wxRemoveFile( fullpath.GetFullPath() ); RefreshSelections(); closed_core.AllowResume(); } } bool Panels::MemoryCardListPanel_Simple::UiDuplicateCard(McdSlotItem& src, McdSlotItem& dest) { wxDirName basepath = GetMcdPath(); if( !src.IsPresent ) { Msgbox::Alert(_("Failed: Can only duplicate an existing card."), _("Duplicate memory card")); return false; } if ( dest.IsPresent && dest.Slot!=-1 ) { wxString content; content.Printf( pxE( "!Notice:Mcd:CantDuplicate", L"Failed: Duplicate is only allowed to an empty PS2-Port or to the file system." ) ); Msgbox::Alert( content, _("Duplicate memory card") ); return false; } while (1) { wxString newFilename=L""; newFilename = wxGetTextFromUser(_("Select a name for the duplicate\n( '.ps2' will be added automatically)"), _("Duplicate memory card")); if( newFilename==L"" ) { //Msgbox::Alert( _("Duplicate canceled"), _("Duplicate memory card") ); return false; } newFilename += L".ps2"; //check that the name is valid for a new file wxString errMsg; if( !isValidNewFilename( newFilename, basepath, errMsg, 5 ) ) { wxString message; message.Printf(_("Failed: %s"), errMsg.c_str()); Msgbox::Alert( message, _("Duplicate memory card") ); continue; } dest.Filename = newFilename; break; } wxFileName srcfile( basepath + src.Filename); wxFileName destfile( basepath + dest.Filename); ScopedBusyCursor doh( Cursor_ReallyBusy ); ScopedCoreThreadClose closed_core; if( !wxCopyFile( srcfile.GetFullPath(), destfile.GetFullPath(), true ) ) { wxString heading; heading.Printf( pxE( "!Notice:Mcd:Copy Failed", L"Failed: Destination memory card '%s' is in use." ), dest.Filename.GetFullName().c_str(), dest.Slot ); wxString content; Msgbox::Alert( heading + L"\n\n" + content, _("Copy failed!") ); closed_core.AllowResume(); return false; } // Destination memcard isEnabled state is the same now as the source's wxString success; success.Printf(_("Memory card '%s' duplicated to '%s'."), src.Filename.GetFullName().c_str(), dest.Filename.GetFullName().c_str() ); Msgbox::Alert(success, _("Success")); dest.IsPresent=true; dest.IsEnabled = true; Apply(); DoRefresh(); closed_core.AllowResume(); return true; } void Panels::MemoryCardListPanel_Simple::UiRenameCard( McdSlotItem& card ) { if( !card.IsPresent ){ Console.WriteLn("Error: Aborted: Rename mcd invoked but no file is associated."); return; } const wxDirName basepath( m_listview->GetMcdProvider().GetMcdPath() ); wxString newFilename; while (1){ wxString title; title.Printf(_("Select a new name for the memory card '%s'\n( '.ps2' will be added automatically)"), card.Filename.GetFullName().c_str() ); newFilename = wxGetTextFromUser(title, _("Rename memory card")); if( newFilename==L"" ) return; newFilename += L".ps2"; //check that the name is valid for a new file wxString errMsg; if( !isValidNewFilename( newFilename, basepath, errMsg, 5 ) ) { wxString message; message.Printf(_("Error (%s)"), errMsg.c_str()); Msgbox::Alert( message, _("Rename memory card") ); continue; } break; } ScopedCoreThreadClose closed_core; bool origEnabled=card.IsEnabled; card.IsEnabled=false; Apply(); if( !wxRenameFile( (basepath + card.Filename).GetFullPath(), (basepath + wxFileName(newFilename)).GetFullPath(), false ) ) { card.IsEnabled=origEnabled; Apply(); Msgbox::Alert( _("Error: Rename could not be completed.\n"), _("Rename memory card") ); closed_core.AllowResume(); return; } card.Filename = newFilename; card.IsEnabled=origEnabled; Apply(); RefreshSelections(); closed_core.AllowResume(); } void Panels::MemoryCardListPanel_Simple::OnCreateOrDeleteCard(wxCommandEvent& evt) { int selectedViewIndex = m_listview->GetFirstSelected(); if( wxNOT_FOUND == selectedViewIndex ) selectedViewIndex = -1;//get filesystem placeholder, just create a new card at the filesystem. McdSlotItem& card( GetCardForViewIndex(selectedViewIndex) ); if( card.IsPresent ) UiDeleteCard( card ); else UiCreateNewCard( card ); } //enable/disapbe port /* void Panels::MemoryCardListPanel_Simple::OnMountCard(wxCommandEvent& evt) { evt.Skip(); const int selectedViewIndex = m_listview->GetFirstSelected(); if( wxNOT_FOUND == selectedViewIndex ) return; McdSlotItem& card( GetCardForViewIndex(selectedViewIndex) ); card.IsEnabled = !card.IsEnabled; m_listview->RefreshItem(selectedViewIndex); UpdateUI(); } */ /* //text dialog: can be used for rename: wxGetTextFromUser - avih void Panels::MemoryCardListPanel_Simple::OnRelocateCard(wxCommandEvent& evt) { evt.Skip(); const int slot = m_listview->GetFirstSelected(); if( wxNOT_FOUND == slot ) return; // Issue a popup to the user that allows them to pick a new .PS2 file to serve as // the new host memorycard file for the slot. The dialog has a number of warnings // present to reiterate that this is an advanced operation that PCSX2 may not // support very well (ie, might be buggy). m_listview->RefreshItem(slot); UpdateUI(); } */ // enter/double-click void Panels::MemoryCardListPanel_Simple::OnItemActivated(wxListEvent& evt) { const int viewIndex = m_listview->GetFirstSelected(); if( wxNOT_FOUND == viewIndex ) return; McdSlotItem& card( GetCardForViewIndex(viewIndex) ); if ( card.IsPresent ) UiRenameCard( card ); else if (card.Slot>=0) UiCreateNewCard( card );// IsPresent==false can only happen for an internal slot (vs filename on the HD), so a card can be created. } void Panels::MemoryCardListPanel_Simple::OnDuplicateFile(wxCommandEvent& evt) { const int viewIndex = m_listview->GetFirstSelected(); if( wxNOT_FOUND == viewIndex ) return; McdSlotItem& card( GetCardForViewIndex(viewIndex) ); pxAssert( card.IsPresent ); McdSlotItem dummy; UiDuplicateCard(card, dummy); } wxString GetPortName(int slotIndex) { if (slotIndex==0 || slotIndex==1) return pxsFmt(wxString(L" ") + _("Port-%u / Multitap-%u--Port-1"), FileMcd_GetMtapPort(slotIndex)+1, FileMcd_GetMtapPort(slotIndex)+1); return pxsFmt(wxString(L" ")+_(" Multitap-%u--Port-%u"), FileMcd_GetMtapPort(slotIndex)+1, FileMcd_GetMtapSlot(slotIndex)+1 ); } void Panels::MemoryCardListPanel_Simple::UiAssignUnassignFile(McdSlotItem &card) { pxAssert( card.IsPresent ); if( card.Slot >=0 ) {//eject card.IsEnabled = false; card.IsPresent = false; card.Filename = L""; DoRefresh(); } else {//insert into a (UI) selected slot wxArrayString selections; int i; for (i=0; i< GetNumVisibleInternalSlots(); i++) { McdSlotItem& selCard = GetCardForViewIndex(i); wxString sel = GetPortName( selCard.Slot ) + L" ( "; if (selCard.IsPresent) sel += selCard.Filename.GetFullName(); else sel += _("Empty"); sel += L" )"; selections.Add(sel); } wxString title; title.Printf(_("Select a target port for '%s'"), card.Filename.GetFullName().c_str()); int res=wxGetSingleChoiceIndex(title, _("Insert card"), selections, this); if( res<0 ) return; McdSlotItem& target = GetCardForViewIndex(res); bool en = target.IsPresent? target.IsEnabled : true; RemoveCardFromSlot( card.Filename ); target.Filename = card.Filename; target.IsPresent = true; target.IsEnabled = en; DoRefresh(); } } void Panels::MemoryCardListPanel_Simple::OnAssignUnassignFile(wxCommandEvent& evt) { const int viewIndex = m_listview->GetFirstSelected(); if( wxNOT_FOUND == viewIndex ) return; McdSlotItem& card( GetCardForViewIndex(viewIndex) ); UiAssignUnassignFile(card); } void Panels::MemoryCardListPanel_Simple::OnRenameFile(wxCommandEvent& evt) { const int viewIndex = m_listview->GetFirstSelected(); if( wxNOT_FOUND == viewIndex ) return; McdSlotItem& card( GetCardForViewIndex(viewIndex) ); UiRenameCard( card ); } void Panels::MemoryCardListPanel_Simple::OnListDrag(wxListEvent& evt) { int selectionViewIndex = m_listview->GetFirstSelected(); if( selectionViewIndex < 0 ) return; McdDataObject my_data( selectionViewIndex ); wxDropSource dragSource( m_listview ); dragSource.SetData( my_data ); /*wxDragResult result = */dragSource.DoDragDrop( wxDrag_AllowMove ); } void Panels::MemoryCardListPanel_Simple::OnListSelectionChanged(wxListEvent& evt) { UpdateUI(); } void Panels::MemoryCardListPanel_Simple::OnOpenItemContextMenu(wxListEvent& evt) { int idx = evt.GetIndex(); wxMenu* junk = new wxMenu(); if( idx != wxNOT_FOUND ) { const McdSlotItem& card( GetCardForViewIndex(idx) ); if (card.IsPresent){ junk->Append( McdMenuId_AssignUnassign, card.Slot>=0?_("Eject card"):_("Insert card ...") ); junk->Append( McdMenuId_Duplicate, _("Duplicate card ...") ); junk->Append( McdMenuId_Rename, _("Rename card ...") ); junk->Append( McdMenuId_Create, _("Delete card") ); } else junk->Append( McdMenuId_Create, _("Create a new card ...") ); } else junk->Append( McdMenuId_Create, _("Create a new card ...") ); junk->AppendSeparator(); junk->Append( McdMenuId_RefreshList, _("Refresh List") ); PopupMenu( junk ); m_listview->RefreshItem( idx ); UpdateUI(); } void Panels::MemoryCardListPanel_Simple::ReadFilesAtMcdFolder(){ //Dir enumeration/iteration code courtesy of cotton. - avih. while( m_allFilesystemCards.size() ) m_allFilesystemCards.pop_back(); m_filesystemPlaceholderCard.Slot=-1; m_filesystemPlaceholderCard.IsEnabled=false; m_filesystemPlaceholderCard.IsPresent=false; m_filesystemPlaceholderCard.Filename=L""; wxArrayString memcardList; wxDir::GetAllFiles(m_FolderPicker->GetPath().ToString(), &memcardList, L"*.ps2", wxDIR_FILES); for(uint i = 0; i < memcardList.size(); i++) { McdSlotItem currentCardFile; bool isOk=EnumerateMemoryCard( currentCardFile, memcardList[i], m_FolderPicker->GetPath() ); if( isOk && !isFileAssignedAndVisibleOnList( currentCardFile.Filename ) ) { currentCardFile.Slot = -1; currentCardFile.IsEnabled = false; m_allFilesystemCards.push_back(currentCardFile); //DevCon.WriteLn(L"Enumerated file: '%s'", currentCardFile.Filename.GetFullName().c_str() ); } /*else DevCon.WriteLn(L"MCD folder card file skipped: '%s'", memcardList[i].c_str() );*/ } } bool Panels::MemoryCardListPanel_Simple::IsSlotVisible(int slotIndex) const { if ( slotIndex<0 || slotIndex>=8 ) return false; if ( !m_MultitapEnabled[0] && 2<=slotIndex && slotIndex<=4) return false; if ( !m_MultitapEnabled[1] && 5<=slotIndex && slotIndex<=7) return false; return true; } //whether or not this filename appears on the ports at the list (takes into account MT enabled/disabled) bool Panels::MemoryCardListPanel_Simple::isFileAssignedAndVisibleOnList(const wxFileName cardFile) const { int i; for( i=0; i<8; i++) if ( IsSlotVisible(i) && cardFile.GetFullName()==m_Cards[i].Filename.GetFullName() ) return true; return false; } //whether or not this filename is assigned to a ports (regardless if MT enabled/disabled) bool Panels::MemoryCardListPanel_Simple::isFileAssignedToInternalSlot(const wxFileName cardFile) const { int i; for( i=0; i<8; i++) if ( cardFile.GetFullName()==m_Cards[i].Filename.GetFullName() ) return true; return false; } void Panels::MemoryCardListPanel_Simple::RemoveCardFromSlot(const wxFileName cardFile) { int i; for( i=0; i<8; i++) if ( cardFile.GetFullName()==m_Cards[i].Filename.GetFullName() ) { m_Cards[i].Filename = L""; m_Cards[i].IsPresent = false; m_Cards[i].IsEnabled = false; } } int Panels::MemoryCardListPanel_Simple::GetNumFilesVisibleAsFilesystem() const { return m_allFilesystemCards.size(); } bool Panels::MemoryCardListPanel_Simple::IsNonEmptyFilesystemCards() const { return GetNumFilesVisibleAsFilesystem()>0; } McdSlotItem& Panels::MemoryCardListPanel_Simple::GetNthVisibleFilesystemCard(int n) { return m_allFilesystemCards.at(n); } int Panels::MemoryCardListPanel_Simple::GetNumVisibleInternalSlots() const { uint baselen = 2; if( m_MultitapEnabled[0] ) baselen += 3; if( m_MultitapEnabled[1] ) baselen += 3; return baselen; } // Interface Implementation for IMcdList int Panels::MemoryCardListPanel_Simple::GetLength() const { uint baselen = GetNumVisibleInternalSlots(); baselen++;//filesystem placeholder baselen+= GetNumFilesVisibleAsFilesystem(); return baselen; } //Translates a list-view index (idx) to an internal memory card slot. //This method effectively defines the arrangement of the card slots at the list view. //The internal card slots array is fixed as sollows: // slot 0: mcd1 (= MT1 slot 1) // slot 1: mcd2 (= MT2 slot 1) // slots 2,3,4: MT1 slots 2,3,4 // slots 5,6,7: MT2 slots 2,3,4 // //however, the list-view doesn't show MT slots when this MT is disabled, // so the view-index should "shift" to point at the real card slot. //While we're at it, we can alternatively enforce any other arrangment of the view by // using any other set of 'view-index-to-card-slot' translating that we'd like. int Panels::MemoryCardListPanel_Simple::GetSlotIndexForViewIndex( int listViewIndex ) { pxAssert( 0<=listViewIndex && listViewIndex=2) { //we got an MT2 slot. assert(listViewIndex < 5); targetSlot = listViewIndex+3; } else { targetSlot=listViewIndex;//identical view-index and card slot. } */ //This arrangement of list-view is as follows: //mcd1(=MT1 port 1) //[MT1 port 2,3,4 if MT1 is enabled] //mcd2(=MT2 slot 1) //[MT2 port 2,3,4 if MT2 is enabled] if (m_MultitapEnabled[0]){ //MT1 enabled: if (1<=listViewIndex && listViewIndex<=3){//MT1 ports 2/3/4 move one place backwards targetSlot=listViewIndex+1; }else if (listViewIndex==4){//mcd2 (=MT2 port 1) moves 3 places forward targetSlot=1; } else {//mcd1 keeps it's pos as first, MT2 ports keep their pos at the end of the list. targetSlot=listViewIndex; } } else { //MT1 disabled: mcd1 and mcd2 stay put, MT2 ports 2,3,4 come next (move backwards 3 places) if (2<=listViewIndex && listViewIndex<=4) targetSlot=listViewIndex+3; else targetSlot=listViewIndex; } pxAssert( 0<=targetSlot && targetSlot<=7 ); return targetSlot; } McdSlotItem& Panels::MemoryCardListPanel_Simple::GetCardForViewIndex( int idx ) { pxAssert( -1<=idx && idx< GetNumVisibleInternalSlots()+1+GetNumFilesVisibleAsFilesystem() ); if( 0<=idx && idxm_filesystemPlaceholderCard; return this->m_allFilesystemCards.at( idx - GetNumVisibleInternalSlots() - 1 ); }