// // This file is part of the aMule Project. // // Copyright (c) 2003-2006 aMule Team ( admin@amule.org / http://www.amule.org ) // Copyright (c) 2002 Merkur ( devs@emule-project.net / http://www.emule-project.net ) // // Any parts of this program derived from the xMule, lMule or eMule project, // or contributed by third-party developers are copyrighted by their // respective authors. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program 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 this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA // #include "SearchListCtrl.h" // Interface declarations #include "MuleNotebook.h" // Needed for CMuleNotebook #include "DownloadQueue.h" // Needed for CDownloadQueue #include "KnownFileList.h" // Needed for CKnownFileList #include "OtherFunctions.h" // Needed for CastItoXBytes #include "PartFile.h" // Needed for CPartFile and CKnownFile #include "SearchList.h" // Needed for CSearchFile #include "SearchDlg.h" // Needed for CSearchDlg #include "amuleDlg.h" // Needed for CamuleDlg #include "OPCodes.h" // Needed for MP_ASSIGNCAT #include "amule.h" // Needed for theApp #include "Color.h" // Needed for BLEND and SYSCOLOR #include "muuli_wdr.h" // Needed for clientImages #include "Preferences.h" // Needed for thePrefs #include #include #include // For arrow-pixmaps #include "pixmaps/sort_dn.xpm" #include "pixmaps/sort_up.xpm" #include "pixmaps/sort_dnx2.xpm" #include "pixmaps/sort_upx2.xpm" BEGIN_EVENT_TABLE(CSearchListCtrl, CMuleListCtrl) EVT_LIST_ITEM_RIGHT_CLICK(-1, CSearchListCtrl::OnRightClick) EVT_LIST_COL_CLICK( -1, CSearchListCtrl::OnColumnLClick) EVT_LIST_COL_END_DRAG( -1, CSearchListCtrl::OnColumnResize) EVT_MENU( MP_GETED2KLINK, CSearchListCtrl::OnPopupGetUrl) EVT_MENU( MP_RAZORSTATS, CSearchListCtrl::OnRazorStatsCheck) EVT_MENU( MP_RESUME, CSearchListCtrl::OnPopupDownload) EVT_MENU_RANGE( MP_ASSIGNCAT, MP_ASSIGNCAT + 99, CSearchListCtrl::OnPopupDownload ) EVT_LIST_ITEM_ACTIVATED( -1, CSearchListCtrl::OnItemActivated) END_EVENT_TABLE() //! Shared list of arrow-pixmaps + static wxImageListType s_imgList(16, 16, true, 0); //! The index of the first rating icon static int s_ratingOffset = 0; std::list CSearchListCtrl::s_lists; enum SearchListColumns { ID_SEARCH_COL_NAME = 0, ID_SEARCH_COL_SIZE, ID_SEARCH_COL_SOURCES, ID_SEARCH_COL_TYPE, ID_SEARCH_COL_FILEID }; CSearchListCtrl::CSearchListCtrl( wxWindow* parent, wxWindowID winid, const wxPoint& pos, const wxSize& size, long style, const wxValidator& validator, const wxString& name ) : CMuleListCtrl( parent, winid, pos, size, style, validator, name), m_filterKnown(false), m_invert(false), m_filterEnabled(false) { // Setting the sorter function. SetSortFunc( SortProc ); InsertColumn( ID_SEARCH_COL_NAME, _("File Name"), wxLIST_FORMAT_LEFT, 500); InsertColumn( ID_SEARCH_COL_SIZE, _("Size"), wxLIST_FORMAT_LEFT, 100); InsertColumn( ID_SEARCH_COL_SOURCES, _("Sources"), wxLIST_FORMAT_LEFT, 50); InsertColumn( ID_SEARCH_COL_TYPE, _("Type"), wxLIST_FORMAT_LEFT, 65); InsertColumn( ID_SEARCH_COL_FILEID, _("FileID"), wxLIST_FORMAT_LEFT, 280); m_nResultsID = 0; // Only load settings for first list, otherwise sync with current lists if ( s_lists.empty() ) { // Set the name to enable loading of settings SetTableName( wxT("Search") ); LoadSettings(); // Unset the name to avoid the settings getting saved every time a list is closed SetTableName( wxEmptyString ); } else { // Sync this list with one of the others SyncLists( s_lists.front(), this ); } // Add the list so that it will be synced with the other lists s_lists.push_back( this ); if (s_imgList.GetImageCount() == 0) { s_imgList.Add(wxBitmap(sort_dn_xpm)); s_imgList.Add(wxBitmap(sort_up_xpm)); s_imgList.Add(wxBitmap(sort_dnx2_xpm)); s_imgList.Add(wxBitmap(sort_upx2_xpm)); // Add rating icons s_ratingOffset = s_imgList.Add(wxBitmap(clientImages(Client_InvalidRating_Smiley))); s_imgList.Add(wxBitmap(clientImages(Client_PoorRating_Smiley))); s_imgList.Add(wxBitmap(clientImages(Client_GoodRating_Smiley))); s_imgList.Add(wxBitmap(clientImages(Client_FairRating_Smiley))); s_imgList.Add(wxBitmap(clientImages(Client_ExcellentRating_Smiley))); } SetImageList(&s_imgList, wxIMAGE_LIST_SMALL); } CSearchListCtrl::~CSearchListCtrl() { std::list::iterator it = std::find( s_lists.begin(), s_lists.end(), this ); if ( it != s_lists.end() ) s_lists.erase( it ); // We only save the settings if the last list was closed if ( s_lists.empty() ) { // In order to get the settings saved, we need to set the name SetTableName( wxT("Search") ); } } void CSearchListCtrl::AddResult(CSearchFile* toshow) { // Ensure that only the right results gets added if ( toshow->GetSearchID() != m_nResultsID ) return; // Check if the result should be shown if (!IsFiltered(toshow)) { m_filteredOut.push_back(toshow); return; } // Rating int image = -1; if (toshow->HasRating()) { image = s_ratingOffset + toshow->UserRating() - 1; } // Insert the item before the item found by the search uint32 newid = InsertItem( GetInsertPos( (long)toshow ), toshow->GetFileName(), image ); SetItemData( newid, (long)toshow ); // Filesize SetItem(newid, ID_SEARCH_COL_SIZE, CastItoXBytes( toshow->GetFileSize() ) ); // Source count wxString temp = wxString::Format( wxT("%d (%d)"), toshow->GetSourceCount(), toshow->GetCompleteSourceCount() ); SetItem( newid, ID_SEARCH_COL_SOURCES, temp ); // File-type SetItem( newid, ID_SEARCH_COL_TYPE, GetFiletypeByName( toshow->GetFileName() ) ); // File-hash SetItem(newid, ID_SEARCH_COL_FILEID, toshow->GetFileHash().Encode() ); // Set the color of the item UpdateItemColor( newid ); } void CSearchListCtrl::UpdateResult(CSearchFile* toupdate) { long index = FindItem(-1, (long)toupdate); if (index != -1) { wxString temp = wxString::Format( wxT("%d (%d)"), toupdate->GetSourceCount(), toupdate->GetCompleteSourceCount()); SetItem(index, ID_SEARCH_COL_SOURCES, temp); UpdateItemColor(index); // Deletions of items causes rather large ammount of flicker, so to // avoid this, we resort the list to ensure correct ordering. if (!IsItemSorted(index)) { SortList(); } } } void CSearchListCtrl::UpdateItemColor( long index ) { wxListItem item; item.SetId( index ); item.SetColumn( ID_SEARCH_COL_SIZE ); item.SetMask( wxLIST_MASK_STATE|wxLIST_MASK_TEXT|wxLIST_MASK_IMAGE|wxLIST_MASK_DATA|wxLIST_MASK_WIDTH|wxLIST_MASK_FORMAT ); if ( GetItem(item) ) { wxColour newcol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); CSearchFile* file = (CSearchFile*)GetItemData(index); CKnownFile* sameFile = theApp.downloadqueue->GetFileByID(file->GetFileHash()); if ( !sameFile ) { sameFile = theApp.knownfiles->FindKnownFileByID(file->GetFileHash()); } int red = newcol.Red(); int green = newcol.Green(); int blue = newcol.Blue(); if ( sameFile ) { if ( sameFile->IsPartFile() ) { // File is already being downloaded. Marks as red. red = 255; } else { // File has already been downloaded. Mark as green. green = 200; } } else { // File is new, colour after number of files blue += file->GetSourceCount() * 5; if ( blue > 255 ) { blue = 255; } } // don't forget to set the item data back... wxListItem newitem; newitem.SetId( index ); newitem.SetTextColour( wxColour( red, green, blue ) ); newitem.SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOX)); SetItem( newitem ); } } void CSearchListCtrl::ShowResults( long ResultsID ) { DeleteAllItems(); m_nResultsID = ResultsID; if ( ResultsID ) { CSearchList::ResultMap::iterator it = theApp.searchlist->m_Results.find( m_nResultsID ); if ( it != theApp.searchlist->m_Results.end() ) { CSearchList::SearchList& list = it->second; for ( unsigned int i = 0; i < list.size(); i++ ) { AddResult( list[i] ); } } } } long CSearchListCtrl::GetSearchId() { return m_nResultsID; } void CSearchListCtrl::SetFilter(const wxString& regExp, bool invert, bool filterKnown) { if (regExp.IsEmpty()) { // Show everything m_filterText = wxT(".*"); } else { m_filterText = regExp; } m_filter.Compile(m_filterText, wxRE_DEFAULT | wxRE_ICASE); m_filterKnown = filterKnown; m_invert = invert; if (m_filterEnabled) { // Swap the list of filtered results so we can freely add new items to the list ResultList curFiltered; std::swap(curFiltered, m_filteredOut); // Filter items already on the list for (int i = 0; i < GetItemCount();) { CSearchFile* file = (CSearchFile*)GetItemData(i); if (IsFiltered(file)) { ++i; } else { m_filteredOut.push_back(file); DeleteItem(i); } } // Check the previously filtered items. ResultList::iterator it = curFiltered.begin(); for (; it != curFiltered.end(); ++it) { if (IsFiltered(*it)) { AddResult(*it); } else { m_filteredOut.push_back(*it); } } } } void CSearchListCtrl::EnableFiltering(bool enabled) { if (enabled != m_filterEnabled) { m_filterEnabled = enabled; if (enabled) { SetFilter(m_filterText, m_invert, m_filterKnown); } else { ResultList::iterator it = m_filteredOut.begin(); for (; it != m_filteredOut.end(); ++it) { AddResult(*it); } m_filteredOut.clear(); } } } size_t CSearchListCtrl::GetHiddenItemCount() const { return m_filteredOut.size(); } bool CSearchListCtrl::IsFiltered(const CSearchFile* file) { // By default, everything is displayed bool result = true; if (m_filterEnabled && m_filter.IsValid()) { result = m_filter.Matches(file->GetFileName()); result = ((result && !m_invert) || (!result && m_invert)); if (result && m_filterKnown) { result = !theApp.downloadqueue->GetFileByID(file->GetFileHash()); if (result) { result = !theApp.knownfiles->FindKnownFileByID(file->GetFileHash()); } } } return result; } int CSearchListCtrl::SortProc(long item1, long item2, long sortData) { CSearchFile* file1 = (CSearchFile*)item1; CSearchFile* file2 = (CSearchFile*)item2; // Modifies the result, 1 for ascending, -1 for decending int modifier = (sortData & CMuleListCtrl::SORT_DES) ? -1 : 1; bool alternate = (sortData & CMuleListCtrl::SORT_ALT); switch (sortData & CMuleListCtrl::COLUMN_MASK) { // Sort by filename case ID_SEARCH_COL_NAME: return modifier * file1->GetFileName().CmpNoCase( file2->GetFileName() ); // Sort file-size case ID_SEARCH_COL_SIZE: return modifier * CmpAny( file1->GetFileSize(), file2->GetFileSize() ); // Sort by sources case ID_SEARCH_COL_SOURCES: { int cmp = CmpAny( file1->GetSourceCount(), file2->GetSourceCount() ); int cmp2 = CmpAny( file1->GetCompleteSourceCount(), file2->GetCompleteSourceCount() ); if ( alternate ) { // Swap criterias int temp = cmp2; cmp2 = cmp; cmp = temp; } if ( cmp == 0 ) { cmp = cmp2; } return modifier * cmp; } // Sort by file-types case ID_SEARCH_COL_TYPE: { wxString fileName1 = wxT(".") + file1->GetFileName().AfterLast('.').MakeLower(); wxString fileName2 = wxT(".") + file2->GetFileName().AfterLast('.').MakeLower(); int result = GetFiletypeByName(fileName1).Cmp(GetFiletypeByName(fileName2)); if (result == 0) { // Same file-type, sort by extension result = fileName1.Cmp(fileName2); } return modifier * result; } // Sort by file-hash case ID_SEARCH_COL_FILEID: return modifier * file2->GetFileHash().Encode().Cmp( file1->GetFileHash().Encode() ); default: return 0; } } void CSearchListCtrl::SyncLists( CSearchListCtrl* src, CSearchListCtrl* dst ) { wxASSERT( src ); wxASSERT( dst ); // Column widths for ( int i = 0; i < src->GetColumnCount(); i++ ) { // We do this check since just setting the width causes a redraw if ( dst->GetColumnWidth( i ) != src->GetColumnWidth( i ) ) { dst->SetColumnWidth( i, src->GetColumnWidth( i ) ); } } // Sync sorting unsigned column = src->GetSortColumn(); unsigned order = src->GetSortOrder(); if (column != dst->GetSortColumn() || order != dst->GetSortOrder()) { dst->SetSorting(column, order); dst->SortList(); } } void CSearchListCtrl::SyncOtherLists( CSearchListCtrl* src ) { std::list::iterator it; for ( it = s_lists.begin(); it != s_lists.end(); ++it ) { if ( (*it) != src ) { SyncLists( src, *it ); } } } void CSearchListCtrl::OnRightClick(wxListEvent& event) { CheckSelection(event); if (GetSelectedItemCount()) { // Create the popup-menu wxMenu menu(_("File")); menu.Append(MP_RESUME, _("Download")); wxMenu* cats = new wxMenu(_("Category")); cats->Append(MP_ASSIGNCAT, _("Main")); for (unsigned i = 1; i < theApp.glob_prefs->GetCatCount(); i++) { cats->Append(MP_ASSIGNCAT + i, theApp.glob_prefs->GetCategory(i)->title); } menu.Append(MP_MENU_CATS, _("Download in category"), cats); menu.AppendSeparator(); /* Commented out while is gone menu.Append(MP_RAZORSTATS, _("Get Razorback 2's stats for this file")); menu.AppendSeparator(); */ menu.Append(MP_GETED2KLINK, _("Copy ED2k link to clipboard")); // These should only be enabled for single-selections bool enable = GetSelectedItemCount(); menu.Enable(MP_GETED2KLINK, enable); menu.Enable(MP_MENU_CATS, (theApp.glob_prefs->GetCatCount() > 1)); PopupMenu(&menu, event.GetPoint()); } else { event.Skip(); } } void CSearchListCtrl::OnColumnLClick( wxListEvent& event ) { // Let the real event handler do its work first CMuleListCtrl::OnColumnLClick( event ); SyncOtherLists( this ); } void CSearchListCtrl::OnColumnResize( wxListEvent& WXUNUSED(event) ) { SyncOtherLists( this ); } void CSearchListCtrl::OnPopupGetUrl( wxCommandEvent& WXUNUSED(event) ) { wxString URIs; long index = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED ); while( index != -1 ) { CSearchFile* file = (CSearchFile*)GetItemData( index ); URIs += theApp.CreateED2kLink( file ) + wxT("\n"); index = GetNextItem( index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED ); } if ( !URIs.IsEmpty() ) { theApp.CopyTextToClipboard( URIs.RemoveLast() ); } } void CSearchListCtrl::OnRazorStatsCheck( wxCommandEvent& WXUNUSED(event) ) { int item = GetNextItem( -1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED ); if ( item == -1 ) { return; } CSearchFile* file = (CSearchFile*)GetItemData( item ); theApp.amuledlg->LaunchUrl(wxT("http://stats.razorback2.com/ed2khistory?ed2k=") + file->GetFileHash().Encode()); } void CSearchListCtrl::OnPopupDownload(wxCommandEvent& event) { if (event.GetId() == MP_RESUME) { // Via the "Download" menu-item, use category specified in drop-down menu DownloadSelected(); } else { // Via an "Download in category" item DownloadSelected(event.GetId() - MP_ASSIGNCAT); } } void CSearchListCtrl::OnItemActivated( wxListEvent& WXUNUSED(event) ) { DownloadSelected(); } bool CSearchListCtrl::AltSortAllowed(unsigned column) const { switch ( column ) { case ID_SEARCH_COL_SOURCES: return true; default: return false; } } void CSearchListCtrl::DownloadSelected(int category) { FindWindowById(IDC_SDOWNLOAD)->Enable(FALSE); // Either the "Download" menu-item, the download-button, double-click or enter if (category == -1) { // Defaults to main category category = 0; if (CastByID(IDC_EXTENDEDSEARCHCHECK, NULL, wxCheckBox)->GetValue()) { category = CastByID(ID_AUTOCATASSIGN, NULL, wxChoice)->GetSelection(); } } long index = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); while (index > -1) { CoreNotify_Search_Add_Download((CSearchFile*)GetItemData(index), category); UpdateItemColor(index); index = GetNextItem(index, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); } }