// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2008 University of California // // BOINC 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 Foundation, // either version 3 of the License, or (at your option) any later version. // // BOINC 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 Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . #define TESTBIGICONPOPUP 0 #define SORTPROJECTLIST 1 #include "stdwx.h" #include "Events.h" #include "app_ipc.h" #include "BOINCGUIApp.h" #include "MainDocument.h" #include "SkinManager.h" #include "wizardex.h" #include "BOINCBaseWizard.h" #include "WizardAttach.h" #include "sg_ProjectPanel.h" #include "str_replace.h" #if TESTBIGICONPOPUP #include "test/sah_40.xpm" #include "test/einstein_icon.xpm" #endif IMPLEMENT_DYNAMIC_CLASS(CSimpleProjectPanel, CSimplePanelBase) BEGIN_EVENT_TABLE(CSimpleProjectPanel, CSimplePanelBase) #ifdef __WXMAC__ EVT_CHOICE(ID_SGPROJECTSELECTOR, CSimpleProjectPanel::OnProjectSelection) #else EVT_COMBOBOX(ID_SGPROJECTSELECTOR, CSimpleProjectPanel::OnProjectSelection) #endif EVT_BUTTON(ID_ADDROJECTBUTTON, CSimpleProjectPanel::OnAddProject) #if TESTBIGICONPOPUP EVT_BUTTON(ID_PROJECTWEBSITESBUTTON, CSimpleProjectPanel::OnProjectWebSiteButton) EVT_BUTTON(ID_PROJECTCOMMANDBUTTON, CSimpleProjectPanel::OnProjectCommandButton) #endif END_EVENT_TABLE() #if TESTBIGICONPOPUP static wxString tempArray[] = {_T("String1"), _T("String2"), _T("String3"), _T("String4") }; static wxBitmap bmArray[3]; #endif CSimpleProjectPanel::CSimpleProjectPanel() { } CSimpleProjectPanel::CSimpleProjectPanel( wxWindow* parent ) : CSimplePanelBase( parent ) { wxSize sz; wxString str = wxEmptyString; wxWindowDC dc(this); m_UsingAccountManager = -1; // Force action the first time m_CurrentSelectedProjectURL[0] = '\0'; m_sAddProjectString = _("Add Project"); m_sSynchronizeString = _("Synchronize"); m_sTotalWorkDoneString = _("Work done for this project"); m_sAddProjectToolTip = _("Volunteer for any or all of 30+ projects in many areas of science"); m_sSynchronizeToolTip = _("Synchronize projects with account manager system"); m_GotBGBitMap = false; // Can't be made until parent has been laid out. SetForegroundColour(*wxBLACK); wxBoxSizer* bSizer1; bSizer1 = new wxBoxSizer( wxVERTICAL ); wxBoxSizer* bSizer2; bSizer2 = new wxBoxSizer( wxHORIZONTAL ); bSizer1->AddSpacer(ADJUSTFORYDPI(5)); m_myProjectsLabel = new CTransparentStaticText( this, wxID_ANY, _("Projects:"), wxDefaultPosition, wxDefaultSize, 0 ); m_myProjectsLabel->Wrap( -1 ); bSizer2->Add( m_myProjectsLabel, 0, wxRIGHT, ADJUSTFORXDPI(5) ); bSizer2->AddStretchSpacer(); int addProjectWidth, synchronizeWidth, y; GetTextExtent(m_sAddProjectString, &addProjectWidth, &y); GetTextExtent(m_sSynchronizeString, &synchronizeWidth, &y); m_TaskAddProjectButton = new CTransparentButton( this, ID_ADDROJECTBUTTON, (addProjectWidth > synchronizeWidth) ? m_sAddProjectString : m_sSynchronizeString, wxDefaultPosition, wxDefaultSize, 0 ); bSizer2->Add( m_TaskAddProjectButton, 0, wxRIGHT | wxEXPAND, SIDEMARGINS ); bSizer1->Add( bSizer2, 0, wxEXPAND | wxTOP | wxLEFT, ADJUSTFORXDPI(10) ); #ifndef __WXMAC__ bSizer1->AddSpacer(ADJUSTFORYDPI(5)); #endif #if TESTBIGICONPOPUP m_ProjectSelectionCtrl = new CBOINCBitmapComboBox( this, ID_SGPROJECTSELECTOR, wxT(""), wxDefaultPosition, wxSize(-1, 42), 4, tempArray, wxCB_READONLY ); CSkinSimple* pSkinSimple = wxGetApp().GetSkinManager()->GetSimple(); bmArray[0] = *pSkinSimple->GetProjectImage()->GetBitmap(); m_ProjectSelectionCtrl->SetItemBitmap(0, bmArray[0]); bmArray[1] = wxBitmap((const char**)sah_40_xpm); m_ProjectSelectionCtrl->SetItemBitmap(1, bmArray[1]); bmArray[2] = wxBitmap((const char**)einstein_icon_xpm); m_ProjectSelectionCtrl->SetItemBitmap(3, bmArray[2]); // m_ProjectSelectionCtrl->SetStringSelection(tempArray[1]); m_ProjectSelectionCtrl->SetSelection(1); #else m_ProjectSelectionCtrl = new CBOINCBitmapComboBox( this, ID_SGPROJECTSELECTOR, wxT(""), wxDefaultPosition, wxSize(-1, 42), 0, NULL, wxCB_READONLY); #endif // TODO: Might want better wording for Project Selection Combo Box tooltip str = _("Select a project to access with the controls below"); m_ProjectSelectionCtrl->SetToolTip(str); bSizer1->Add( m_ProjectSelectionCtrl, 0, wxLEFT | wxRIGHT | wxEXPAND, SIDEMARGINS ); #ifndef __WXMAC__ bSizer1->AddSpacer(ADJUSTFORYDPI(8)); #endif // Make sure m_TotalCreditValue string is large enough m_fDisplayedCredit = 9999999999.99; str.Printf(wxT("%s: %.0f"), m_sTotalWorkDoneString.c_str(), m_fDisplayedCredit); m_TotalCreditValue = new CTransparentStaticText( this, wxID_ANY, str, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE ); m_TotalCreditValue->Wrap( -1 ); bSizer1->Add( m_TotalCreditValue, 0, wxLEFT | wxRIGHT | wxEXPAND, SIDEMARGINS ); bSizer1->AddSpacer(ADJUSTFORYDPI(5)); wxBoxSizer* bSizer3; bSizer3 = new wxBoxSizer( wxHORIZONTAL ); m_ProjectWebSitesButton = new CSimpleProjectWebSitesPopupButton( this, ID_PROJECTWEBSITESBUTTON, _("Project Web Pages"), wxDefaultPosition, wxDefaultSize, 0 ); bSizer3->Add( m_ProjectWebSitesButton, 0, wxEXPAND | wxALIGN_LEFT, 0 ); bSizer3->AddStretchSpacer(); m_ProjectCommandsButton = new CSimpleProjectCommandPopupButton( this, ID_PROJECTCOMMANDBUTTON, _("Project Commands"), wxDefaultPosition, wxDefaultSize, 0 ); bSizer3->Add( m_ProjectCommandsButton, 0, wxEXPAND, 0 ); bSizer1->Add( bSizer3, 0, wxLEFT | wxRIGHT | wxEXPAND, SIDEMARGINS ); bSizer1->AddSpacer(ADJUSTFORYDPI(10)); // Temporarily insert a dummy entry so sizer can // get correct height of m_ProjectSelectionCtrl CSkinSimple* pSkinSimple = wxGetApp().GetSkinManager()->GetSimple(); wxBitmap* defaultBM = pSkinSimple->GetProjectImage()->GetBitmap(); m_ProjectSelectionCtrl->Insert("", *defaultBM, 0, (void*)NULL); this->SetSizer( bSizer1 ); this->Layout(); // Remove the dummy entry m_ProjectSelectionCtrl->Delete(0); m_TaskAddProjectButton->SetToolTip(wxEmptyString); m_TaskAddProjectButton->Disable(); } CSimpleProjectPanel::~CSimpleProjectPanel() { ProjectSelectionData *selData; int count = m_ProjectSelectionCtrl->GetCount(); for(int j = count-1; j >=0; --j) { selData = (ProjectSelectionData*)m_ProjectSelectionCtrl->GetClientData(j); delete selData; // Indicate to Clear() we have cleaned up the Selection Data m_ProjectSelectionCtrl->SetClientData(j, NULL); } m_ProjectSelectionCtrl->Clear(); } ProjectSelectionData* CSimpleProjectPanel::GetProjectSelectionData() { int count = m_ProjectSelectionCtrl->GetCount(); if (count <= 0) { return NULL; } int n = m_ProjectSelectionCtrl->GetSelection(); return (ProjectSelectionData*)m_ProjectSelectionCtrl->GetClientData(n); } void CSimpleProjectPanel::UpdateInterface() { int n, count = -1; bool b_needMenuRebuild = false; wxString str = wxEmptyString; wxString projName = wxEmptyString; CMainDocument* pDoc = wxGetApp().GetDocument(); wxASSERT(pDoc); // Should we display the synchronize button instead of the // attach to project button? if ( pDoc->IsConnected() ) { CC_STATUS status; int is_acct_mgr_detected = 0; pDoc->GetCoreClientStatus(status); if (pDoc->m_iAcct_mgr_info_rpc_result == 0) { // We use an integer rather than a bool to force action the first time is_acct_mgr_detected = pDoc->ami.acct_mgr_url.size() ? 1 : 0; if ((m_UsingAccountManager != is_acct_mgr_detected) || (!m_TaskAddProjectButton->IsEnabled())) { m_UsingAccountManager = is_acct_mgr_detected; if (is_acct_mgr_detected) { m_TaskAddProjectButton->SetLabel(m_sSynchronizeString); m_TaskAddProjectButton->Enable(); m_TaskAddProjectButton->SetToolTip(m_sSynchronizeToolTip); } else { m_TaskAddProjectButton->SetLabel(m_sAddProjectString); if (!status.disallow_attach) { m_TaskAddProjectButton->Enable(); m_TaskAddProjectButton->SetToolTip(m_sAddProjectToolTip); } } this->Layout(); } } else { m_TaskAddProjectButton->Disable(); } UpdateProjectList(); count = m_ProjectSelectionCtrl->GetCount(); } if (count > 0) { n = m_ProjectSelectionCtrl->GetSelection(); if ((n < 0) || (n > count -1)) { m_ProjectSelectionCtrl->SetSelection(0); n = 0; } // Check to see if we need to rebuild the menu char* ctrl_url = ((ProjectSelectionData*)m_ProjectSelectionCtrl->GetClientData(n))->project_url; if (strcmp(m_CurrentSelectedProjectURL, ctrl_url)) { b_needMenuRebuild = true; strlcpy(m_CurrentSelectedProjectURL, ctrl_url, sizeof(m_CurrentSelectedProjectURL)); } PROJECT* project = pDoc->state.lookup_project(ctrl_url); if ( project != NULL && project->last_rpc_time > m_Project_last_rpc_time ) { b_needMenuRebuild = true; m_Project_last_rpc_time = project->last_rpc_time; } if (b_needMenuRebuild) { m_ProjectWebSitesButton->RebuildMenu(); } m_ProjectWebSitesButton->Enable(); m_ProjectCommandsButton->Enable(); if (m_fDisplayedCredit != project->user_total_credit) { str.Printf(wxT("%s: %s"), m_sTotalWorkDoneString.c_str(), format_number(project->user_total_credit, 0) ); UpdateStaticText(&m_TotalCreditValue, str); m_TotalCreditValue->SetName(str); // For accessibility on Windows } projName = m_ProjectSelectionCtrl->GetStringSelection(); str.Printf(_("Pop up a menu of web sites for project %s"), projName.c_str()); m_ProjectWebSitesButton->SetToolTip(str); str.Printf(_("Pop up a menu of commands to apply to project %s"), projName.c_str()); m_ProjectCommandsButton->SetToolTip(str); } else { m_ProjectWebSitesButton->Disable(); m_ProjectCommandsButton->Disable(); m_CurrentSelectedProjectURL[0] = '\0'; m_fDisplayedCredit = -1.0; UpdateStaticText(&m_TotalCreditValue, wxEmptyString); m_TotalCreditValue->SetName(wxEmptyString); // For accessibility on Windows m_ProjectWebSitesButton->SetToolTip(wxEmptyString); m_ProjectCommandsButton->SetToolTip(wxEmptyString); } } void CSimpleProjectPanel::ReskinInterface() { wxLogTrace(wxT("Function Start/End"), wxT("CSimpleProjectPanel::ReskinInterface - Function Begin")); CMainDocument* pDoc = wxGetApp().GetDocument(); ProjectSelectionData* selData; PROJECT* project; char* ctrl_url; CSimplePanelBase::ReskinInterface(); // Check to see if we need to reload the project icon int ctrlCount = m_ProjectSelectionCtrl->GetCount(); for(int j=0; jGetClientData(j); ctrl_url = selData->project_url; project = pDoc->state.lookup_project(ctrl_url); wxBitmap* projectBM = GetProjectSpecificBitmap(ctrl_url); m_ProjectSelectionCtrl->SetItemBitmap(j, *projectBM); } wxLogTrace(wxT("Function Start/End"), wxT("CSimpleProjectPanel::ReskinInterface - Function Begin")); } void CSimpleProjectPanel::OnAddProject(wxCommandEvent& event) { if (m_UsingAccountManager) { OnWizardUpdate(); } else { OnWizardAttach(event); } } void CSimpleProjectPanel::OnWizardAttach(wxCommandEvent& event) { wxLogTrace(wxT("Function Start/End"), wxT("CProjectsComponent::OnWizardAttach - Function Begin")); CMainDocument* pDoc = wxGetApp().GetDocument(); CSimpleGUIPanel* pPanel = wxDynamicCast(GetParent(), CSimpleGUIPanel); wxASSERT(pDoc); wxASSERT(pPanel); wxASSERT(wxDynamicCast(pDoc, CMainDocument)); if (!pDoc->IsUserAuthorized()) return; if (!pDoc->IsConnected()) return; pPanel->SetDlgOpen(true); pPanel->OnProjectsAttachToProject(event); // btnAddProj->Refresh(); pPanel->SetDlgOpen(false); wxLogTrace(wxT("Function Start/End"), wxT("CProjectsComponent::OnWizardAttach - Function End")); } void CSimpleProjectPanel::OnWizardUpdate() { wxLogTrace(wxT("Function Start/End"), wxT("CProjectsComponent::OnWizardUpdate - Function Begin")); CMainDocument* pDoc = wxGetApp().GetDocument(); CSimpleGUIPanel* pPanel = wxDynamicCast(GetParent(), CSimpleGUIPanel); wxASSERT(pDoc); wxASSERT(pPanel); wxASSERT(wxDynamicCast(pDoc, CMainDocument)); if (!pDoc->IsUserAuthorized()) return; if (!pDoc->IsConnected()) return; pPanel->SetDlgOpen(true); CWizardAttach* pWizard = new CWizardAttach(this); pWizard->SyncToAccountManager(); if (pWizard) pWizard->Destroy(); // btnSynchronize->Refresh(); pPanel->SetDlgOpen(false); wxLogTrace(wxT("Function Start/End"), wxT("CProjectsComponent::OnWizardUpdate - Function End")); } #if TESTBIGICONPOPUP void CSimpleProjectPanel::OnProjectWebSiteButton(wxCommandEvent& /*event*/) { m_ProjectSelectionCtrl->Delete(0); /*** CAF *** FOR TESTING ONLY ***/ } void CSimpleProjectPanel::OnProjectCommandButton(wxCommandEvent& /*event*/) { /*** CAF *** FOR TESTING ONLY ***/ static int i = 1; wxString s; if (++i > 8) i = 0; int sel = i % 3; // m_ProjectSelectionCtrl->SetStringSelection(tempArray[sel]); m_ProjectSelectionCtrl->SetSelection(sel); #if 0 //TESTBIGICONPOPUP wxRect r; wxWindowDC dc(this); r = m_ProjectSelectionCtrl->GetRect(); dc.DrawBitmap(bmArray[sel], r.x + 9, r.y, false); #endif } #endif void CSimpleProjectPanel::OnProjectSelection(wxCommandEvent& /*event*/) { UpdateInterface(); // const int sel = m_ProjectSelectionCtrl->GetSelection(); #if 0 //TESTBIGICONPOPUP wxRect r; wxWindowDC dc(this); r = m_ProjectSelectionCtrl->GetRect(); dc.DrawBitmap(bmArray[sel], r.x + 9, r.y - 10, false); #endif } void CSimpleProjectPanel::UpdateProjectList() { CMainDocument* pDoc = wxGetApp().GetDocument(); ProjectSelectionData* selData; PROJECT* project; int oldProjectSelection, newProjectSelection; if ( pDoc->IsConnected() ) { int projCnt = pDoc->GetSimpleProjectCount(); int ctrlCount = m_ProjectSelectionCtrl->GetCount(); oldProjectSelection = m_ProjectSelectionCtrl->GetSelection(); // If a new project has been added, figure out which one for(int i=0; istate.projects[i]; bool found = false; for(int j=0; jGetClientData(j))->project_url; if (!strcmp(project->master_url, ctrl_url)) { found = true; break; } } // add new project // if (!found) { const char* p = project->project_name.c_str(); if (strlen(p) == 0) { p = project->master_url; } wxString projname(p, wxConvUTF8); #if SORTPROJECTLIST int alphaOrder,j; for(j = 0; j < ctrlCount; ++j) { alphaOrder = (m_ProjectSelectionCtrl->GetString(j)).CmpNoCase(projname); if (alphaOrder > 0) { break; // Insert the new item here (sorted by item label) } } #endif selData = new ProjectSelectionData; strlcpy(selData->project_url, project->master_url, sizeof(selData->project_url)); selData->project_files_downloaded_time = project->project_files_downloaded_time; wxBitmap* projectBM = GetProjectSpecificBitmap(selData->project_url); #if SORTPROJECTLIST if (j < ctrlCount) { m_ProjectSelectionCtrl->Insert(projname, *projectBM, j, (void*)selData); if (j <= oldProjectSelection) { ++oldProjectSelection; m_ProjectSelectionCtrl->SetSelection(oldProjectSelection); } } else #endif { m_ProjectSelectionCtrl->Append(projname, *projectBM, (void*)selData); } ctrlCount = m_ProjectSelectionCtrl->GetCount(); } } newProjectSelection = oldProjectSelection; if ( projCnt < ctrlCount ) { project = NULL; // Check items in descending order so deletion won't change indexes of items yet to be checked for(int j=ctrlCount-1; j>=0; --j) { char* ctrl_url = ((ProjectSelectionData*)m_ProjectSelectionCtrl->GetClientData(j))->project_url; project = pDoc->state.lookup_project(ctrl_url); if ( project == NULL ) { selData = (ProjectSelectionData*)m_ProjectSelectionCtrl->GetClientData(j); delete selData; // Indicate to Delete() we have cleaned up the Selection Data m_ProjectSelectionCtrl->SetClientData(j, NULL); m_ProjectSelectionCtrl->Delete(j); if (j == oldProjectSelection) { int newCount = m_ProjectSelectionCtrl->GetCount(); if (newProjectSelection < newCount) { // Select the next item if one exists m_ProjectSelectionCtrl->SetSelection(newProjectSelection); } else if (newCount > 0) { // Select the previous item if one exists newProjectSelection = newCount-1; m_ProjectSelectionCtrl->SetSelection(newProjectSelection); } else { newProjectSelection = -1; m_ProjectSelectionCtrl->SetSelection(wxNOT_FOUND); } } } } } // Check to see if we need to reload the project icon ctrlCount = m_ProjectSelectionCtrl->GetCount(); for(int j=0; jGetClientData(j); char* ctrl_url = selData->project_url; project = pDoc->state.lookup_project(ctrl_url); if ( (project != NULL) && (project->project_files_downloaded_time > selData->project_files_downloaded_time) ) { wxBitmap* projectBM = GetProjectSpecificBitmap(ctrl_url); selData->project_files_downloaded_time = project->project_files_downloaded_time; m_ProjectSelectionCtrl->SetItemBitmap(j, *projectBM); } } } } std::string CSimpleProjectPanel::GetProjectIconLoc(char* project_url) { char proj_dir[256]; CMainDocument* pDoc = wxGetApp().GetDocument(); PROJECT* project = pDoc->state.lookup_project(project_url); if (!project) return (std::string)""; url_to_project_dir(project->master_url, proj_dir, sizeof(proj_dir)); return (std::string)proj_dir + "/stat_icon"; } wxBitmap* CSimpleProjectPanel::GetProjectSpecificBitmap(char* project_url) { char defaultIcnPath[256]; CSkinSimple* pSkinSimple = wxGetApp().GetSkinManager()->GetSimple(); wxASSERT(pSkinSimple); // Only update it if project specific is found if(boinc_resolve_filename(GetProjectIconLoc(project_url).c_str(), defaultIcnPath, sizeof(defaultIcnPath)) == 0) { wxBitmap* projectBM; wxString strIconPath = wxString(defaultIcnPath,wxConvUTF8); if (wxFile::Exists(strIconPath)) { // wxBitmapComboBox requires all its bitmaps to be the same size // Our "project icon" bitmaps should all be 40 X 40 wxImage img = wxImage(strIconPath, wxBITMAP_TYPE_ANY); if (img.IsOk()) { #ifdef __WXMSW__ if ((GetXDPIScaling() > 1.05) || (GetYDPIScaling() > 1.05)) { img.Rescale((int) (40*GetXDPIScaling()), (int) (40*GetYDPIScaling()), wxIMAGE_QUALITY_BILINEAR ); } #else if ((img.GetHeight() != 40) || (img.GetWidth() == 40)) { img.Rescale(40, 40, wxIMAGE_QUALITY_BILINEAR); } #endif projectBM = new wxBitmap(img); if (projectBM->IsOk()) { return projectBM; } } } } return pSkinSimple->GetProjectImage()->GetBitmap(); }