Go back to earlier strategy for net selector popup.

This version makes use of lots of things learned going down the
other rat hole.  Avoiding the wxComboFocusHandler is key, as well
as specifying using the AltPopupWindow.

The key handler is still tricky with respect to those platforms
that use native controls, but the starting-key strategy is similar
to the one used with wxGrid text editors.
This commit is contained in:
Jeff Young 2018-09-29 22:04:32 +01:00
parent 3c2aafd7b7
commit 2eea45b50c
2 changed files with 209 additions and 280 deletions

View File

@ -48,177 +48,128 @@ wxDEFINE_EVENT( NET_SELECTED, wxCommandEvent );
#define NO_NET _( "<no net>" )
class POPUP_EVENTFILTER : public wxEventFilter
class NET_SELECTOR_COMBOPOPUP : public wxPanel, public wxComboPopup
{
public:
POPUP_EVENTFILTER( wxDialog* aPopup, wxComboCtrl* aCombobox ) :
m_popup( aPopup ),
m_combobox( aCombobox ),
m_firstMouseUp( false )
NET_SELECTOR_COMBOPOPUP() :
m_minPopupWidth( -1 ),
m_maxPopupHeight( 1000 ),
m_netinfoList( nullptr ),
m_selectedNetcode( 0 )
{ }
bool Create(wxWindow* aParent) override
{
wxEvtHandler::AddFilter( this );
}
wxPanel::Create( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER );
~POPUP_EVENTFILTER() override
{
wxEvtHandler::RemoveFilter( this );
}
wxBoxSizer* mainSizer;
mainSizer = new wxBoxSizer( wxVERTICAL );
int FilterEvent( wxEvent& aEvent ) override
{
if( aEvent.GetEventType() == wxEVT_LEFT_DOWN )
{
// Click outside popup cancels
if( !m_popup->GetScreenRect().Contains( wxGetMousePosition() ) )
{
m_popup->EndModal( wxID_CANCEL );
return Event_Processed;
}
}
else if( aEvent.GetEventType() == wxEVT_LEFT_UP )
{
if( m_firstMouseUp )
{
// A first mouse-up inside the popup represents a drag-style menu selection
if( m_popup->GetScreenRect().Contains( wxGetMousePosition() ) )
m_popup->EndModal( wxID_OK );
wxStaticText* filterLabel = new wxStaticText( this, wxID_ANY, _( "Filter:" ) );
mainSizer->Add( filterLabel, 0, wxEXPAND, 0 );
// Otherwise the first mouse-up is sent back to the combox button
else if( m_combobox->GetButton() )
m_combobox->GetButton()->GetEventHandler()->ProcessEvent( aEvent );
m_firstMouseUp = false;
return Event_Processed;
}
}
return Event_Skip;
}
private:
wxDialog* m_popup;
wxComboCtrl* m_combobox;
bool m_firstMouseUp;
};
class NET_SELECTOR_POPUP : public wxDialog
{
public:
NET_SELECTOR_POPUP( wxComboCtrl* aParent, const wxPoint& aPos, const wxSize& aSize,
NETINFO_LIST* aNetInfoList ) :
wxDialog( aParent->GetParent(), wxID_ANY, wxEmptyString, aPos, aSize,
wxWANTS_CHARS ),
m_parentCombobox( aParent ),
m_minPopupWidth( aSize.x ),
m_maxPopupHeight( aSize.y ),
m_netinfoList( aNetInfoList ),
m_initialized( false ),
m_selectedNetcode( 0 ),
m_retCode( 0 )
{
SetExtraStyle( wxWS_EX_BLOCK_EVENTS|wxWS_EX_PROCESS_IDLE );
auto mainSizer = new wxBoxSizer( wxVERTICAL );
auto panelSizer = new wxBoxSizer( wxVERTICAL );
auto panel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxSIMPLE_BORDER );
mainSizer->Add( panel, 1, wxEXPAND, 0 );
wxStaticText* title = new wxStaticText( panel, wxID_ANY, _( "Filter:" ) );
panelSizer->Add( title, 0, wxEXPAND, 0 );
m_filterCtrl = new wxTextCtrl( panel, wxID_ANY, wxEmptyString, wxDefaultPosition,
m_filterCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxDefaultSize, wxTE_PROCESS_ENTER );
panelSizer->Add( m_filterCtrl, 0, wxEXPAND, 0 );
mainSizer->Add( m_filterCtrl, 0, wxEXPAND, 0 );
m_listBox = new wxListBox( panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, 0,
wxLB_NEEDED_SB );
panelSizer->Add( m_listBox, 0, wxEXPAND|wxTOP, 2 );
m_listBox = new wxListBox( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, 0,
wxLB_SINGLE|wxLB_NEEDED_SB );
mainSizer->Add( m_listBox, 0, wxEXPAND|wxTOP, 2 );
panel->SetSizer( panelSizer );
this->SetSizer( mainSizer );
SetSizer( mainSizer );
Layout();
Connect( wxEVT_IDLE, wxIdleEventHandler( NET_SELECTOR_POPUP::onIdle ), NULL, this );
Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR_POPUP::onKeyDown ), NULL, this );
m_listBox->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( NET_SELECTOR_POPUP::onListBoxMouseClick ), NULL, this );
m_filterCtrl->Connect( wxEVT_TEXT, wxCommandEventHandler( NET_SELECTOR_POPUP::onFilterEdit ), NULL, this );
m_filterCtrl->Connect( wxEVT_TEXT_ENTER, wxCommandEventHandler( NET_SELECTOR_POPUP::onEnter ), NULL, this );
Connect( wxEVT_IDLE, wxIdleEventHandler( NET_SELECTOR_COMBOPOPUP::onIdle ), NULL, this );
Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR_COMBOPOPUP::onKeyDown ), NULL, this );
Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( NET_SELECTOR_COMBOPOPUP::onMouseClick ), NULL, this );
m_listBox->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( NET_SELECTOR_COMBOPOPUP::onMouseClick ), NULL, this );
m_filterCtrl->Connect( wxEVT_TEXT, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onFilterEdit ), NULL, this );
m_filterCtrl->Connect( wxEVT_TEXT_ENTER, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onEnter ), NULL, this );
// <enter> in a ListBox comes in as a double-click on GTK
m_listBox->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( NET_SELECTOR_POPUP::onEnter ), NULL, this );
m_listBox->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onEnter ), NULL, this );
return true;
}
wxWindow *GetControl() override { return this; }
void SetStringValue( const wxString& aNetName ) override
{
// shouldn't be here (combo is read-only)
}
wxString GetStringValue() const override
{
NETINFO_ITEM* netInfo = m_netinfoList->GetNetItem( m_selectedNetcode );
if( netInfo && netInfo->GetNet() > 0 )
return netInfo->GetNetname();
return NO_NET;
}
void SetNetInfo( NETINFO_LIST* aNetInfoList )
{
m_netinfoList = aNetInfoList;
rebuildList();
}
~NET_SELECTOR_POPUP()
{
Disconnect( wxEVT_IDLE, wxIdleEventHandler( NET_SELECTOR_POPUP::onIdle ), NULL, this );
Disconnect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR_POPUP::onKeyDown ), NULL, this );
m_listBox->Disconnect( wxEVT_LEFT_DOWN, wxMouseEventHandler( NET_SELECTOR_POPUP::onListBoxMouseClick ), NULL, this );
m_filterCtrl->Disconnect( wxEVT_TEXT, wxCommandEventHandler( NET_SELECTOR_POPUP::onFilterEdit ), NULL, this );
m_filterCtrl->Disconnect( wxEVT_TEXT_ENTER, wxCommandEventHandler( NET_SELECTOR_POPUP::onEnter ), NULL, this );
void SetIndeterminate() { m_selectedNetcode = -1; }
bool IsIndeterminate() { return m_selectedNetcode == -1; }
m_listBox->Disconnect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( NET_SELECTOR_POPUP::onEnter ), NULL, this );
void SetSelectedNetcode( int aNetcode ) { m_selectedNetcode = aNetcode; }
int GetSelectedNetcode() { return m_selectedNetcode; }
wxSize GetAdjustedSize( int aMinWidth, int aPrefHeight, int aMaxHeight ) override
{
// Called when the popup is first shown. Stash the minWidth and maxHeight so we
// can use them later when refreshing the sizes after filter changes.
m_minPopupWidth = aMinWidth;
m_maxPopupHeight = aMaxHeight;
return updateSize();
}
void SetSelectedNetcode( int aNetcode )
void OnPopup() override
{
m_selectedNetcode = aNetcode;
m_listBox->SetFocus();
// The updateSize() call in GetAdjustedSize() leaves the height off-by-one for
// some reason, so do it again.
updateSize();
}
int GetSelectedNetcode()
void Accept()
{
return m_selectedNetcode;
}
wxString selectedNetName;
int selection = m_listBox->GetSelection();
// While we act like a modal our implementation is not modal. This is done to allow us
// to catch mouse and key events outside our window.
int ShowModal() override
{
POPUP_EVENTFILTER filter( this, m_parentCombobox );
if( selection >= 0 )
selectedNetName = m_listBox->GetString( (unsigned) selection );
Show( true );
doSetFocus( m_listBox );
while( !m_retCode )
wxYield();
return m_retCode;
}
void EndModal( int aReason ) override
{
if( IsShown() )
Show( false );
if( !m_retCode )
if( selectedNetName.IsEmpty() )
{
if( aReason == wxID_OK )
{
wxString selectedNetName;
int selection = m_listBox->GetSelection();
if( selection >= 0 )
selectedNetName = m_listBox->GetString( (unsigned) selection );
if( selectedNetName.IsEmpty() )
aReason = wxID_CANCEL;
else if( selectedNetName == NO_NET )
m_selectedNetcode = 0;
else
m_selectedNetcode = m_netinfoList->GetNetItem( selectedNetName )->GetNet();
}
m_retCode = aReason;
m_selectedNetcode = -1;
GetComboCtrl()->SetValue( INDETERMINATE );
}
else if( selectedNetName == NO_NET )
{
m_selectedNetcode = 0;
GetComboCtrl()->SetValue( NO_NET );
}
else
{
m_selectedNetcode = m_netinfoList->GetNetItem( selectedNetName )->GetNet();
GetComboCtrl()->SetValue( selectedNetName );
}
wxCommandEvent changeEvent( NET_SELECTED );
wxPostEvent( GetComboCtrl(), changeEvent );
Dismiss();
}
protected:
void updateSize()
wxSize updateSize()
{
int listTop = m_listBox->GetRect().y;
int itemHeight = GetTextSize( wxT( "Xy" ), this ).y + LIST_ITEM_PADDING;
@ -235,10 +186,16 @@ protected:
listWidth = std::max( listWidth, itemWidth + LIST_PADDING * 3 );
}
m_listBox->SetMinSize( wxSize( listWidth, listHeight ) );
m_listBox->SetSize( wxSize( listWidth, listHeight ) );
wxSize listSize( listWidth, listHeight );
wxSize popupSize( listWidth, listTop + listHeight );
SetSize( wxSize( listWidth, listTop + listHeight ) );
SetSize( popupSize ); // us
GetParent()->SetSize( popupSize ); // the window that wxComboCtrl put us in
m_listBox->SetMinSize( listSize );
m_listBox->SetSize( listSize );
return popupSize;
}
void rebuildList()
@ -263,9 +220,6 @@ protected:
netNames.insert( netNames.begin(), NO_NET );
m_listBox->Set( netNames );
updateSize();
m_listBox->Refresh();
}
void onIdle( wxIdleEvent& aEvent )
@ -279,24 +233,6 @@ protected:
lastPos = screenPos;
onMouseMoved( screenPos );
}
#ifndef __WXGTK__
// Check for loss of focus. This will indicate that a window manager processed
// an activate event without fully involving wxWidgets (and thus our EventFilter
// never got notified of the click).
// Note: don't try to do this with KillFocus events; the event ordering is too
// platform-dependant.
if( m_initialized )
{
bool focusFound = false;
for( wxWindow* w = wxWindow::FindFocus(); w && !focusFound; w = w->GetParent() )
focusFound = ( w == this );
if( !focusFound )
EndModal( wxID_CANCEL );
}
#endif
}
// Hot-track the mouse (for focus and listbox selection)
@ -316,10 +252,33 @@ protected:
{
doSetFocus( m_filterCtrl );
}
else if( !m_initialized )
}
void onMouseClick( wxMouseEvent& aEvent )
{
// Accept a click event from anywhere. Different platform implementations have
// different foibles with regard to transient popups and their children.
if( aEvent.GetEventObject() == m_listBox )
{
doSetFocus( m_listBox );
m_initialized = true;
m_listBox->SetSelection( m_listBox->HitTest( aEvent.GetPosition() ) );
Accept();
return;
}
wxWindow* window = dynamic_cast<wxWindow*>( aEvent.GetEventObject() );
if( window )
{
wxPoint screenPos = window->ClientToScreen( aEvent.GetPosition() );
if( m_listBox->GetScreenRect().Contains( screenPos ) )
{
wxPoint localPos = m_listBox->ScreenToClient( screenPos );
m_listBox->SetSelection( m_listBox->HitTest( localPos ) );
Accept();
}
}
}
@ -327,21 +286,23 @@ protected:
{
switch( aEvent.GetKeyCode() )
{
// Control keys go to the parent combobox
case WXK_TAB:
EndModal( wxID_CANCEL );
Dismiss();
m_parent->NavigateIn( ( aEvent.ShiftDown() ? 0 : wxNavigationKeyEvent::IsForward ) |
( aEvent.ControlDown() ? wxNavigationKeyEvent::WinChange : 0 ) );
break;
case WXK_ESCAPE:
EndModal( wxID_CANCEL );
Dismiss();
break;
case WXK_RETURN:
EndModal( wxID_OK );
Accept();
break;
// Arrows go to the list box
case WXK_DOWN:
case WXK_NUMPAD_DOWN:
doSetFocus( m_listBox );
@ -354,26 +315,72 @@ protected:
m_listBox->SetSelection( std::max( m_listBox->GetSelection() - 1, 0 ) );
break;
// Everything else goes to the filter textbox
default:
aEvent.Skip();
if( !m_filterCtrl->HasFocus() )
{
doSetFocus( m_filterCtrl );
// We already missed our chance to have the native widget handle it. We'll
// have to do the first character ourselves.
onStartingKey( aEvent );
}
else
{
// On some platforms a wxComboFocusHandler will have been pushed which
// unhelpfully gives the event right back to the popup. Make sure the filter
// control is going to get the event.
if( m_filterCtrl->GetEventHandler() != m_filterCtrl )
m_filterCtrl->PushEventHandler( m_filterCtrl );
aEvent.Skip();
}
break;
}
}
void onEnter( wxCommandEvent& aEvent )
{
EndModal( wxID_OK );
Accept();
}
void onStartingKey( wxKeyEvent& aEvent )
{
if( aEvent.GetKeyCode() == WXK_BACK )
{
const long pos = m_filterCtrl->GetLastPosition();
m_filterCtrl->Remove( pos - 1, pos );
}
else
{
bool isPrintable;
int ch = aEvent.GetUnicodeKey();
if( ch != WXK_NONE )
isPrintable = true;
else
{
ch = aEvent.GetKeyCode();
isPrintable = ch >= WXK_SPACE && ch < WXK_START;
}
if( isPrintable )
{
wxString text( static_cast<wxChar>( ch ) );
// wxCHAR_HOOK chars have been converted to uppercase.
if( !aEvent.ShiftDown() )
text.MakeLower();
m_filterCtrl->WriteText( text );
}
}
}
void onFilterEdit( wxCommandEvent& aEvent )
{
rebuildList();
}
void onListBoxMouseClick( wxMouseEvent& aEvent )
{
m_listBox->SetSelection( m_listBox->HitTest( aEvent.GetPosition() ) );
EndModal( wxID_OK );
updateSize();
}
void doSetFocus( wxWindow* aWindow )
@ -386,120 +393,51 @@ protected:
}
protected:
wxComboCtrl* m_parentCombobox;
int m_minPopupWidth;
int m_maxPopupHeight;
NETINFO_LIST* m_netinfoList;
wxTextCtrl* m_filterCtrl;
wxListBox* m_listBox;
int m_minPopupWidth;
int m_maxPopupHeight;
wxTextCtrl* m_filterCtrl;
wxListBox* m_listBox;
NETINFO_LIST* m_netinfoList;
bool m_initialized;
int m_selectedNetcode;
int m_retCode;
int m_selectedNetcode;
};
NET_SELECTOR::NET_SELECTOR( wxWindow *parent, wxWindowID id,
const wxPoint &pos, const wxSize &size, long style ) :
wxComboCtrl( parent, id, wxEmptyString, pos, size, style|wxCB_READONLY ),
m_netinfoList( nullptr ),
m_netcode( -1 ),
m_netSelectorPopup( nullptr )
NET_SELECTOR::NET_SELECTOR( wxWindow *parent, wxWindowID id, const wxPoint &pos,
const wxSize &size, long style ) :
wxComboCtrl( parent, id, wxEmptyString, pos, size, style|wxCB_READONLY )
{
Connect( wxEVT_COMBOBOX_DROPDOWN, wxCommandEventHandler( NET_SELECTOR::onDropdown ), NULL, this );
UseAltPopupWindow();
m_netSelectorPopup = new NET_SELECTOR_COMBOPOPUP();
SetPopupControl( m_netSelectorPopup );
}
NET_SELECTOR::~NET_SELECTOR()
{
delete m_netSelectorPopup;
}
void NET_SELECTOR::DoSetPopupControl( wxComboPopup* )
{
m_popup = nullptr;
}
void NET_SELECTOR::onDropdown( wxCommandEvent& )
{
// Not all keyboard activation methods seem to get to OnButtonClick() by themselves,
// so we pick them up here.
OnButtonClick();
}
void NET_SELECTOR::OnButtonClick()
{
// Guard against clicks during show or hide
if( m_netSelectorPopup )
return;
wxRect comboRect = GetScreenRect();
wxPoint popupPos( comboRect.x + POPUP_PADDING, comboRect.y + comboRect.height );
wxDisplay display( (unsigned) wxDisplay::GetFromWindow( this ) );
wxSize popupSize( comboRect.width - ( POPUP_PADDING * 2 ),
display.GetClientArea().y + display.GetClientArea().height - popupPos.y );
m_netSelectorPopup = new NET_SELECTOR_POPUP( this, popupPos, popupSize, m_netinfoList );
m_netSelectorPopup->SetSelectedNetcode( m_netcode );
if( m_netSelectorPopup->ShowModal() == wxID_OK )
SetSelectedNetcode( m_netSelectorPopup->GetSelectedNetcode() );
SetFocus();
delete m_netSelectorPopup;
m_netSelectorPopup = nullptr;
}
void NET_SELECTOR::SetNetInfo( NETINFO_LIST* aNetInfoList )
{
m_netinfoList = aNetInfoList;
m_netSelectorPopup->SetNetInfo( aNetInfoList );
}
void NET_SELECTOR::SetSelectedNetcode( int aNetcode )
{
m_netcode = aNetcode;
wxASSERT( m_netinfoList );
if( m_netcode == -1 )
SetValue( INDETERMINATE );
else if( m_netinfoList )
{
NETINFO_ITEM* netInfo = m_netinfoList->GetNetItem( m_netcode );
if( netInfo && netInfo->GetNet() > 0 )
SetValue( netInfo->GetNetname() );
else
SetValue( NO_NET );
}
m_netSelectorPopup->SetSelectedNetcode( aNetcode );
SetValue( m_netSelectorPopup->GetStringValue() );
}
void NET_SELECTOR::SetIndeterminate()
{
m_netcode = -1;
m_netSelectorPopup->SetIndeterminate();
SetValue( INDETERMINATE );
}
bool NET_SELECTOR::IsIndeterminate()
{
return m_netcode == -1;
return m_netSelectorPopup->IsIndeterminate();
}
int NET_SELECTOR::GetSelectedNetcode()
{
return m_netcode;
return m_netSelectorPopup->GetSelectedNetcode();
}

View File

@ -30,7 +30,7 @@
class BOARD;
class NETINFO_LIST;
class NET_SELECTOR_POPUP;
class NET_SELECTOR_COMBOPOPUP;
wxDECLARE_EVENT( NET_SELECTED, wxCommandEvent );
@ -41,10 +41,9 @@ class NET_SELECTOR : public wxComboCtrl
public:
// Note: this list of arguments is here because it keeps us from having to customize
// the constructor calls in wxFormBuilder.
NET_SELECTOR( wxWindow *parent, wxWindowID id, const wxPoint &pos = wxDefaultPosition,
const wxSize &size = wxDefaultSize, long style = 0 );
~NET_SELECTOR() override;
NET_SELECTOR( wxWindow *parent, wxWindowID id,
const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize,
long style = 0 );
void SetNetInfo( NETINFO_LIST* aNetInfoList );
@ -54,16 +53,8 @@ public:
bool IsIndeterminate();
int GetSelectedNetcode();
protected:
void onDropdown( wxCommandEvent& aEvent );
void OnButtonClick() override;
void DoSetPopupControl( wxComboPopup* aPopup ) override;
NETINFO_LIST* m_netinfoList;
int m_netcode;
NET_SELECTOR_POPUP* m_netSelectorPopup;
NET_SELECTOR_COMBOPOPUP* m_netSelectorPopup;
};