diff --git a/checkin_notes b/checkin_notes index 72de5f583b..bc27870071 100644 --- a/checkin_notes +++ b/checkin_notes @@ -6053,3 +6053,23 @@ David 27 June 2009 db/ schema.sql + +Rom 29 June 2009 + - MGR: My first attempt to handle the project list control was turning + into a mess and becoming very complicated, as my former mentor used + to say "if you are having to try too hard, you are doing it wrong." + + Reimplement the project list control as a wxHtmlListBox which handles + keyboard navigation correctly and allows us to have variable height + list items. Another perk is the ability to detect when different + HTML elements are clicked on which might be useful for anonymous + attach at some point in time in the future. + + clientgui/ + AccountManagerInfoPage.cpp + AdvancedFrame.cpp + BOINCGUIApp.cpp + BOINCWizards.h + ProjectInfoPage.cpp + ProjectListCtrl.cpp, .h + stdwx.h diff --git a/clientgui/AccountManagerInfoPage.cpp b/clientgui/AccountManagerInfoPage.cpp index 0f6607f706..1d10d0e56c 100644 --- a/clientgui/AccountManagerInfoPage.cpp +++ b/clientgui/AccountManagerInfoPage.cpp @@ -263,8 +263,9 @@ void CAccountManagerInfoPage::OnPageChanged( wxWizardExEvent& event ) { ); m_pProjectListCtrl->Append( - wxString(pl.account_managers[i]->name.c_str(), wxConvUTF8), wxString(pl.account_managers[i]->url.c_str(), wxConvUTF8), + wxString(pl.account_managers[i]->name.c_str(), wxConvUTF8), + wxString(pl.account_managers[i]->description.c_str(), wxConvUTF8), true ); } diff --git a/clientgui/AdvancedFrame.cpp b/clientgui/AdvancedFrame.cpp index 3f911a4281..3ed43fae0c 100644 --- a/clientgui/AdvancedFrame.cpp +++ b/clientgui/AdvancedFrame.cpp @@ -54,18 +54,12 @@ #include "BOINCWizards.h" #include "BOINCBaseWizard.h" #include "WizardAttachProject.h" -//#include "WizardAccountManager.h" #include "DlgAdvPreferences.h" #include "res/connect.xpm" #include "res/disconnect.xpm" -// Which of the view sets should we display. -// -//#define VIEW_LIST 2 - - enum STATUSBARFIELDS { STATUS_TEXT, STATUS_CONNECTION_STATUS diff --git a/clientgui/BOINCGUIApp.cpp b/clientgui/BOINCGUIApp.cpp index 712ae8adb3..597fecb44a 100644 --- a/clientgui/BOINCGUIApp.cpp +++ b/clientgui/BOINCGUIApp.cpp @@ -312,6 +312,7 @@ bool CBOINCGUIApp::OnInit() { wxInitAllImageHandlers(); // Enable additional file system type handlers + wxFileSystem::AddHandler(new wxMemoryFSHandler); #if wxUSE_FS_INET && wxUSE_STREAMS && wxUSE_SOCKETS wxFileSystem::AddHandler(new wxInternetFSHandler); #endif diff --git a/clientgui/BOINCWizards.h b/clientgui/BOINCWizards.h index 51b9ce9b19..fc03218070 100644 --- a/clientgui/BOINCWizards.h +++ b/clientgui/BOINCWizards.h @@ -71,10 +71,9 @@ #define ID_WELCOMECHANGEAPPS 11102 // Project Info/Account Manager Info Controls -#define ID_PROJECTSELECTIONCTRL 11200 +#define ID_PROJECTLISTCTRL 11200 #define ID_PROJECTURLSTATICCTRL 11201 -#define ID_PROJECTURLDESCRIPTIONSTATICCTRL 11202 -#define ID_PROJECTURLCTRL 11203 +#define ID_PROJECTURLCTRL 11202 // Terms Of Use Controls #define ID_TERMSOFUSECTRL 11300 diff --git a/clientgui/ProjectInfoPage.cpp b/clientgui/ProjectInfoPage.cpp index b35429851f..6e7e26e424 100644 --- a/clientgui/ProjectInfoPage.cpp +++ b/clientgui/ProjectInfoPage.cpp @@ -286,11 +286,19 @@ void CProjectInfoPage::OnPageChanged( wxWizardExEvent& event ) { ); m_pProjectListCtrl->Append( - wxString(pl.projects[i]->name.c_str(), wxConvUTF8), wxString(pl.projects[i]->url.c_str(), wxConvUTF8), + wxString(pl.projects[i]->name.c_str(), wxConvUTF8), + wxString(pl.projects[i]->description.c_str(), wxConvUTF8), bSupportedPlatformFound ); } + + // Pre select the first element + m_pProjectListCtrl->SetSelection(0); + m_strProjectURL = m_pProjectListCtrl->GetItem(0)->GetURL(); + m_bProjectSupported = m_pProjectListCtrl->GetItem(0)->IsPlatformSupported(); + TransferDataToWindow(); + m_bProjectListPopulated = true; } diff --git a/clientgui/ProjectListCtrl.cpp b/clientgui/ProjectListCtrl.cpp index 1de039d636..68b5e5edd2 100644 --- a/clientgui/ProjectListCtrl.cpp +++ b/clientgui/ProjectListCtrl.cpp @@ -21,6 +21,7 @@ #include "stdwx.h" #include "BOINCGUIApp.h" +#include "BOINCWizards.h" #include "ProjectListCtrl.h" ////@begin XPM images @@ -28,30 +29,325 @@ ////@end XPM images +#ifdef wxUSE_ACCESSIBILITY + +// Gets the name of the specified object. +wxAccStatus CProjectListCtrlAccessible::GetName(int childId, wxString* name) +{ + if (childId == wxACC_SELF) + { + *name = wxT("Project List"); + } + else + { + CProjectListCtrl* pCtrl = wxDynamicCast(GetWindow(), CProjectListCtrl); + if (pCtrl) + { + *name = pCtrl->GetItem(childId - 1)->GetTitle().c_str(); + } + } + return wxACC_OK; +} + + +// Can return either a child object, or an integer +// representing the child element, starting from 1. +wxAccStatus CProjectListCtrlAccessible::HitTest(const wxPoint& pt, int* childId, wxAccessible** /*childObject*/) +{ + CProjectListCtrl* pCtrl = wxDynamicCast(GetWindow(), CProjectListCtrl); + if (pCtrl) + { + *childId = pCtrl->HitTest(pt); + return wxACC_OK; + } + // Let the framework handle the other cases. + return wxACC_NOT_IMPLEMENTED; +} + + +// Returns the rectangle for this object (id = 0) or a child element (id > 0). +wxAccStatus CProjectListCtrlAccessible::GetLocation(wxRect& rect, int elementId) +{ + CProjectListCtrl* pCtrl = wxDynamicCast(GetWindow(), CProjectListCtrl); + if (pCtrl && (0 != elementId)) + { + wxSize cCtrlSize = pCtrl->GetClientSize(); + int iItemWidth = cCtrlSize.GetWidth(); + int iItemHeight = pCtrl->GetTotalClientHeight() / (int)pCtrl->GetItemCount(); + + // Set the initial control postition to the absolute coords of the upper + // left hand position of the control + rect.SetPosition(pCtrl->GetScreenPosition()); + rect.width = iItemWidth - 1; + rect.height = iItemHeight - 1; + + if (1 == elementId) + { + // First child + } + else + { + // Other children + rect.SetTop(rect.GetTop() + ((elementId - 1) * iItemHeight) + 1); + rect.height -= 1; + } + return wxACC_OK; + } + // Let the framework handle the other cases. + return wxACC_NOT_IMPLEMENTED; +} + + +// Navigates from fromId to toId/toObject. +wxAccStatus CProjectListCtrlAccessible::Navigate( + wxNavDir navDir, int fromId, int* toId, wxAccessible** toObject +) { + + CProjectListCtrl* pCtrl = wxDynamicCast(GetWindow(), CProjectListCtrl); + *toObject = NULL; + + if (0 != fromId) + { + switch (navDir) + { + case wxNAVDIR_PREVIOUS: + case wxNAVDIR_UP: + if (1 == fromId) + { + return wxACC_FALSE; + } + else + { + *toId = fromId - 1; + return wxACC_OK; + } + break; + case wxNAVDIR_NEXT: + case wxNAVDIR_DOWN: + if ((int)pCtrl->GetItemCount() == fromId) + { + return wxACC_FALSE; + } + else + { + *toId = fromId + 1; + return wxACC_OK; + } + return wxACC_FALSE; + break; + case wxNAVDIR_LEFT: + return wxACC_FALSE; + break; + case wxNAVDIR_RIGHT: + return wxACC_FALSE; + break; + case wxNAVDIR_FIRSTCHILD: + if (1 == fromId) + { + return wxACC_FALSE; + } + else + { + *toId = 1; + return wxACC_OK; + } + break; + case wxNAVDIR_LASTCHILD: + if ((int)pCtrl->GetItemCount() == fromId) + { + return wxACC_FALSE; + } + else + { + *toId = (int)pCtrl->GetItemCount(); + return wxACC_OK; + } + break; + } + } + // Let the framework handle the other cases. + return wxACC_NOT_IMPLEMENTED; +} + + +// Gets the number of children. +wxAccStatus CProjectListCtrlAccessible::GetChildCount(int* childCount) +{ + CProjectListCtrl* pCtrl = wxDynamicCast(GetWindow(), CProjectListCtrl); + if (pCtrl) + { + *childCount = (int)pCtrl->GetItemCount(); + return wxACC_OK; + } + // Let the framework handle the other cases. + return wxACC_NOT_IMPLEMENTED; +} + + +// Performs the default action. childId is 0 (the action for this object) +// or > 0 (the action for a child). +// Return wxACC_NOT_SUPPORTED if there is no default action for this +// window (e.g. an edit control). +wxAccStatus CProjectListCtrlAccessible::DoDefaultAction(int childId) +{ + CProjectListCtrl* pCtrl = wxDynamicCast(GetWindow(), CProjectListCtrl); + if (pCtrl && (childId != wxACC_SELF)) + { + // Zero-based array index + int iRealChildId = childId - 1; + + pCtrl->SetSelection(iRealChildId); + + // Fire Event + ProjectListCtrlEvent evt( + wxEVT_PROJECTLIST_ITEM_CHANGE, + pCtrl->GetItem(iRealChildId)->GetTitle(), + pCtrl->GetItem(iRealChildId)->GetURL(), + true + ); + evt.SetEventObject(this); + + pCtrl->GetParent()->AddPendingEvent( evt ); + + return wxACC_OK; + } + // Let the framework handle the other cases. + return wxACC_NOT_IMPLEMENTED; +} + + +// Gets the default action for this object (0) or > 0 (the action for a child). +// Return wxACC_OK even if there is no action. actionName is the action, or the empty +// string if there is no action. +// The retrieved string describes the action that is performed on an object, +// not what the object does as a result. For example, a toolbar button that prints +// a document has a default action of "Press" rather than "Prints the current document." +wxAccStatus CProjectListCtrlAccessible::GetDefaultAction(int childId, wxString* actionName) +{ + CProjectListCtrl* pCtrl = wxDynamicCast(GetWindow(), CProjectListCtrl); + if (pCtrl && (childId != wxACC_SELF)) + { + *actionName = _("Click"); + return wxACC_OK; + } + // Let the framework handle the other cases. + return wxACC_NOT_IMPLEMENTED; +} + + +// Returns the description for this object or a child. +wxAccStatus CProjectListCtrlAccessible::GetDescription(int childId, wxString* description) +{ + CProjectListCtrl* pCtrl = wxDynamicCast(GetWindow(), CProjectListCtrl); + if (pCtrl && (childId != wxACC_SELF)) + { + *description = pCtrl->GetItem(childId - 1)->GetDescription().c_str(); + return wxACC_OK; + } + // Let the framework handle the other cases. + return wxACC_NOT_IMPLEMENTED; +} + + +// Returns a role constant. +wxAccStatus CProjectListCtrlAccessible::GetRole(int childId, wxAccRole* role) +{ + if (childId == wxACC_SELF) + { + *role = wxROLE_SYSTEM_LIST; + } + else + { + *role = wxROLE_SYSTEM_LISTITEM; + } + return wxACC_OK; +} + + +// Returns a role constant. +wxAccStatus CProjectListCtrlAccessible::GetState(int childId, long* state) +{ + if (childId == wxACC_SELF) + { + *state = wxACC_STATE_SYSTEM_DEFAULT; + } + else + { + CProjectListCtrl* pCtrl = wxDynamicCast(GetWindow(), CProjectListCtrl); + if (pCtrl && (pCtrl->IsSelected(childId - 1))) + { + *state = wxACC_STATE_SYSTEM_SELECTED | wxACC_STATE_SYSTEM_FOCUSED; + } + else if (pCtrl && (pCtrl->IsVisible(childId - 1))) + { + *state = wxACC_STATE_SYSTEM_SELECTABLE; + } + else + { + *state = wxACC_STATE_SYSTEM_SELECTABLE | wxACC_STATE_SYSTEM_INVISIBLE; + } + } + return wxACC_OK; +} + + +// Selects the object or child. +wxAccStatus CProjectListCtrlAccessible::Select(int childId, wxAccSelectionFlags selectFlags) +{ + // Let the framework handle the other cases. + return wxACC_NOT_IMPLEMENTED; +} + + +// Gets a variant representing the selected children +// of this object. +// Acceptable values: +// - a null variant (IsNull() returns true) +// - a list variant (GetType() == wxT("list")) +// - an integer representing the selected child element, +// or 0 if this object is selected (GetType() == wxT("long")) +// - a "void*" pointer to a wxAccessible child object +wxAccStatus CProjectListCtrlAccessible::GetSelections(wxVariant* selections) +{ + // Let the framework handle the other cases. + return wxACC_NOT_IMPLEMENTED; +} + +#endif + + +/*! + * CProjectListItem type definition + */ +IMPLEMENT_DYNAMIC_CLASS( CProjectListItem, wxObject ) + + /*! * CProjectListCtrl event definitions */ DEFINE_EVENT_TYPE( wxEVT_PROJECTLIST_ITEM_CHANGE ) DEFINE_EVENT_TYPE( wxEVT_PROJECTLIST_ITEM_DISPLAY ) + /*! * CProjectListCtrl type definition */ - -IMPLEMENT_DYNAMIC_CLASS( CProjectListCtrl, wxScrolledWindow ) +IMPLEMENT_DYNAMIC_CLASS( CProjectListCtrl, wxHtmlListBox ) IMPLEMENT_DYNAMIC_CLASS( ProjectListCtrlEvent, wxNotifyEvent ) + /*! * CProjectListCtrl event table definition */ -BEGIN_EVENT_TABLE( CProjectListCtrl, wxScrolledWindow ) +BEGIN_EVENT_TABLE( CProjectListCtrl, wxHtmlListBox ) ////@begin CProjectListCtrl event table entries - EVT_SET_FOCUS( CProjectListCtrl::OnFocusChanged ) - EVT_KILL_FOCUS( CProjectListCtrl::OnFocusChanged ) - EVT_KEY_DOWN( CProjectListCtrl::OnKeyPressed ) - EVT_KEY_UP( CProjectListCtrl::OnKeyPressed ) + EVT_LISTBOX(ID_PROJECTLISTCTRL, CProjectListCtrl::OnSelected) + EVT_HTML_CELL_CLICKED( ID_PROJECTLISTCTRL, CProjectListCtrl::OnClicked ) + EVT_LISTBOX_DCLICK(ID_PROJECTLISTCTRL, CProjectListCtrl::OnDClicked) + EVT_HTML_LINK_CLICKED( ID_PROJECTLISTCTRL, CProjectListCtrl::OnLinkClicked ) + EVT_HTML_CELL_HOVER( ID_PROJECTLISTCTRL, CProjectListCtrl::OnHover ) ////@end CProjectListCtrl event table entries END_EVENT_TABLE() @@ -76,203 +372,86 @@ CProjectListCtrl::CProjectListCtrl( wxWindow* parent ) bool CProjectListCtrl::Create( wxWindow* parent ) { ////@begin CProjectListCtrl member initialisation - m_pMainSizer = NULL; ////@end CProjectListCtrl member initialisation - m_pCurrentSelection = NULL; - + ////@begin CProjectListCtrl creation - wxScrolledWindow::Create( parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, - wxSUNKEN_BORDER | wxVSCROLL | wxTAB_TRAVERSAL ); + wxHtmlListBox::Create( parent, ID_PROJECTLISTCTRL, wxDefaultPosition, wxDefaultSize, + wxSUNKEN_BORDER | wxTAB_TRAVERSAL ); - SetMinSize(GetSize()); +#ifdef wxUSE_ACCESSIBILITY + SetAccessible(new CProjectListCtrlAccessible(this)); +#endif - CreateControls(); - - SetBackgroundColour( wxT("WHITE") ); - SetScrollRate( 0, 25 ); - - GetSizer()->Fit(this); + wxMemoryFSHandler::AddFile(wxT("webexternallink.xpm"), wxBitmap(externalweblink_xpm), wxBITMAP_TYPE_XPM); ////@end CProjectListCtrl creation return TRUE; } - -/*! - * Control creation for ProjectListCtrl - */ - -void CProjectListCtrl::CreateControls() -{ -////@begin CProjectListCtrl content construction - wxFlexGridSizer* itemFlexGridSizer5 = new wxFlexGridSizer(0, 1, 0, 0); - itemFlexGridSizer5->AddGrowableCol(0); - wxFlexGridSizer* itemFlexGridSizer6 = new wxFlexGridSizer(1, 1, 0, 0); - itemFlexGridSizer6->AddGrowableRow(0); - itemFlexGridSizer6->AddGrowableCol(0); - itemFlexGridSizer5->Add(itemFlexGridSizer6, 0, wxGROW|wxGROW|wxALL, 0); +void CProjectListCtrl::OnSelected( wxCommandEvent& event ) +{ + // Fire Event + ProjectListCtrlEvent evt( + wxEVT_PROJECTLIST_ITEM_CHANGE, + m_Items[event.GetInt()]->GetTitle(), + m_Items[event.GetInt()]->GetURL(), + m_Items[event.GetInt()]->IsPlatformSupported() + ); + evt.SetEventObject(this); - m_pMainSizer = new wxBoxSizer(wxVERTICAL); - itemFlexGridSizer6->Add(m_pMainSizer, 0, wxGROW|wxGROW|wxALL, 5); - - SetSizer(itemFlexGridSizer5); - -////@end CProjectListCtrl content construction + GetParent()->AddPendingEvent( evt ); } -/*! - * event handler for window - */ - -void CProjectListCtrl::OnItemChange( CProjectListItemCtrl* pSelectedItem ) { - - // Fire an event for the parent window notifing it of the new selection. - if (pSelectedItem) { - - // Store so we know where we are - m_pCurrentSelection = pSelectedItem; - - // Fire Event - ProjectListCtrlEvent evt( - wxEVT_PROJECTLIST_ITEM_CHANGE, - pSelectedItem->GetTitle(), - pSelectedItem->GetURL(), - pSelectedItem->IsSupported() - ); - evt.SetEventObject(this); - - GetParent()->AddPendingEvent( evt ); - } - -} - - -/*! - * event handler for window - */ - -void CProjectListCtrl::OnItemDisplay( wxCommandEvent& event ) { - - // Fire an event for the parent window notifing it to display more information. - CProjectListItemCtrl* pItem = wxDynamicCast(event.GetEventObject(), CProjectListItemCtrl); - if (pItem) { - - // Fire Event - ProjectListCtrlEvent evt( - wxEVT_PROJECTLIST_ITEM_DISPLAY, - pItem->GetTitle(), - pItem->GetURL(), - pItem->IsSupported() - ); - evt.SetEventObject(this); - - GetParent()->AddPendingEvent( evt ); - } - -} - - -/*! - * event handler for window - */ - -void CProjectListCtrl::OnItemFocusChange( CProjectListItemCtrl* pSelectedItem ) { - - // Reset the background color back to the default - wxWindowList::compatibility_iterator current = GetChildren().GetFirst(); - while (current) { - wxWindow* child = current->GetData(); - - child->SetBackgroundColour( wxNullColour ); - child->Refresh(); - - current = current->GetNext(); - } - - // Set the background color of the window that threw the event to the - // default background color of a selected control. - if (pSelectedItem) { - pSelectedItem->SetBackgroundColour( wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT) ); - pSelectedItem->Refresh(); - - OnItemChange( pSelectedItem ); - } - -} - - -/*! - * wxEVT_SET_FOCUS, wxEVT_KILL_FOCUS event handler for window - */ - -void CProjectListCtrl::OnFocusChanged( wxFocusEvent& event ) { - - if ( wxEVT_SET_FOCUS == event.GetEventType() ) { - // Control has focus - if (!m_pCurrentSelection) { - // Set the focus to the first child - GetChildren().GetFirst()->GetData()->SetFocus(); - } - } +void CProjectListCtrl::OnClicked( wxHtmlCellEvent& event ) +{ event.Skip(); - } -/*! - * wxEVT_KEY_DOWN, wxEVT_KEY_UP event handler for window - */ +void CProjectListCtrl::OnDClicked( wxCommandEvent& event ) +{ + event.Skip(); +} -void CProjectListCtrl::OnKeyPressed( wxKeyEvent& event ) { - wxWindowList list; - list = GetChildren(); - if ( (wxEVT_KEY_DOWN == event.GetEventType()) && (WXK_DOWN == event.GetKeyCode()) ) { +void CProjectListCtrl::OnLinkClicked( wxHtmlLinkEvent& event ) +{ + // Fire Event + ProjectListCtrlEvent evt( + wxEVT_PROJECTLIST_ITEM_DISPLAY, + wxEmptyString, + event.GetLinkInfo().GetHref(), + true + ); + evt.SetEventObject(this); - wxWindowList::compatibility_iterator iter = GetChildren().GetLast(); - while (iter) { - wxWindow* pCurentWindow = iter->GetData(); - if (pCurentWindow == m_pCurrentSelection) { - wxWindowListNode* pNextNode = iter->GetNext(); - if (pNextNode) { - wxWindow* pNextWindow = pNextNode->GetData(); - if (pNextWindow) { - pNextWindow->SetFocus(); - } - } - } - iter = iter->GetPrevious(); - } + GetParent()->AddPendingEvent( evt ); +} - } else if ( (wxEVT_KEY_DOWN == event.GetEventType()) && (WXK_UP == event.GetKeyCode()) ) { - wxWindowList::compatibility_iterator iter = GetChildren().GetFirst(); - while (iter) { - wxWindow* pCurentWindow = iter->GetData(); - if (pCurentWindow == m_pCurrentSelection) { - wxWindowListNode* pPreviousNode = iter->GetPrevious(); - if (pPreviousNode) { - wxWindow* pPreviousWindow = pPreviousNode->GetData(); - if (pPreviousWindow) { - pPreviousWindow->SetFocus(); - } - } - } - iter = iter->GetNext(); - } +void CProjectListCtrl::OnHover( wxHtmlCellEvent& event ) +{ + event.Skip(); +} - } else if ( (wxEVT_KEY_DOWN == event.GetEventType()) && (WXK_TAB == event.GetKeyCode()) ) { - if (wxMOD_SHIFT == event.GetModifiers()) { - Navigate( wxNavigationKeyEvent::IsBackward & wxNavigationKeyEvent::WinChange ); - } else { - Navigate( wxNavigationKeyEvent::IsForward & wxNavigationKeyEvent::WinChange ); - } +wxString CProjectListCtrl::OnGetItem(size_t i) const +{ + wxString buf = wxEmptyString; - } else { - event.Skip(); - } + buf.Printf( + wxT("
%s | ") + wxT("") + wxT(" |