KiCad PCB EDA Suite
Loading...
Searching...
No Matches
filter_combobox.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24
26
27#include <wx/textctrl.h>
28#include <wx/listbox.h>
29#include <wx/settings.h>
30#include <wx/sizer.h>
31#include <wx/stattext.h>
32#include <wx/valtext.h>
33
34#include <kiplatform/ui.h>
35#include <widgets/ui_common.h>
36
37wxDEFINE_EVENT( FILTERED_ITEM_SELECTED, wxCommandEvent );
38
39#if defined( __WXOSX_MAC__ )
40 #define POPUP_PADDING 2
41 #define LIST_ITEM_PADDING 5
42 #define LIST_PADDING 5
43#elif defined( __WXMSW__ )
44 #define POPUP_PADDING 0
45 #define LIST_ITEM_PADDING 2
46 #define LIST_PADDING 5
47#else
48 #define POPUP_PADDING 0
49 #define LIST_ITEM_PADDING 6
50 #define LIST_PADDING 5
51#endif
52
53
55 m_filterValidator( nullptr ),
56 m_filterCtrl( nullptr ),
57 m_listBox( nullptr ),
58 m_minPopupWidth( -1 ),
59 m_maxPopupHeight( 1000 ),
60 m_focusHandler( nullptr )
61{
62}
63
64
65bool FILTER_COMBOPOPUP::Create( wxWindow* aParent )
66{
67 wxPanel::Create( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER );
68
69 wxBoxSizer* mainSizer;
70 mainSizer = new wxBoxSizer( wxVERTICAL );
71
72 wxStaticText* filterLabel = new wxStaticText( this, wxID_ANY, _( "Filter:" ) );
73 mainSizer->Add( filterLabel, 0, wxEXPAND, 0 );
74
75 m_filterCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
76 wxTE_PROCESS_ENTER );
77 m_filterValidator = new wxTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
78 m_filterValidator->SetCharExcludes( " " );
79 m_filterCtrl->SetValidator( *m_filterValidator );
80 mainSizer->Add( m_filterCtrl, 0, wxEXPAND, 0 );
81
82 m_listBox = new wxListBox( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr,
83 wxLB_SINGLE | wxLB_NEEDED_SB );
84 mainSizer->Add( m_listBox, 0, wxEXPAND | wxTOP, 2 );
85
86 SetSizer( mainSizer );
87 Layout();
88
89 Connect( wxEVT_IDLE, wxIdleEventHandler( FILTER_COMBOPOPUP::onIdle ), nullptr, this );
90 Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( FILTER_COMBOPOPUP::onKeyDown ), nullptr, this );
91 Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( FILTER_COMBOPOPUP::onMouseClick ), nullptr,
92 this );
93 m_listBox->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( FILTER_COMBOPOPUP::onMouseClick ),
94 nullptr, this );
95 m_filterCtrl->Connect( wxEVT_TEXT, wxCommandEventHandler( FILTER_COMBOPOPUP::onFilterEdit ),
96 nullptr, this );
97 m_filterCtrl->Connect( wxEVT_TEXT_ENTER, wxCommandEventHandler( FILTER_COMBOPOPUP::onEnter ),
98 nullptr, this );
99
100 // <enter> in a ListBox comes in as a double-click on GTK
101 m_listBox->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED,
102 wxCommandEventHandler( FILTER_COMBOPOPUP::onEnter ), nullptr, this );
103
104 return true;
105}
106
107
108void FILTER_COMBOPOPUP::SetStringValue( const wxString& aNetName )
109{
110 // shouldn't be here (combo is read-only)
111}
112
113
115{
116 // While it can sometimes be useful to keep the filter, it's always unexpected.
117 // Better to clear it.
118 m_filterCtrl->Clear();
119
120 m_listBox->SetStringSelection( GetStringValue() );
121 m_filterCtrl->SetFocus();
122
123 // The updateSize() call in GetAdjustedSize() leaves the height off-by-one for
124 // some reason, so do it again.
125 updateSize();
126}
127
128
129void FILTER_COMBOPOPUP::OnStartingKey( wxKeyEvent& aEvent )
130{
132 doStartingKey( aEvent );
133}
134
135
136wxSize FILTER_COMBOPOPUP::GetAdjustedSize( int aMinWidth, int aPrefHeight, int aMaxHeight )
137{
138 // Called when the popup is first shown. Stash the minWidth and maxHeight so we
139 // can use them later when refreshing the sizes after filter changes.
140 m_minPopupWidth = aMinWidth;
141 m_maxPopupHeight = aMaxHeight;
142
143 return updateSize();
144}
145
146
148{
149 wxArrayString newList;
150 getListContent( newList );
151
152 m_listBox->Set( newList );
153}
154
155
156std::optional<wxString> FILTER_COMBOPOPUP::getSelectedValue() const
157{
158 int selection = m_listBox->GetSelection();
159
160 if( selection >= 0 )
161 return m_listBox->GetString( selection );
162
163 return std::nullopt;
164}
165
166
168{
169 return m_filterCtrl->GetValue().Trim( true ).Trim( false );
170}
171
172
174{
175 int listTop = m_listBox->GetRect().y;
176 int itemHeight = KIUI::GetTextSize( wxT( "Xy" ), this ).y + LIST_ITEM_PADDING;
177 int listHeight = m_listBox->GetCount() * itemHeight + LIST_PADDING;
178
179 if( listTop + listHeight >= m_maxPopupHeight )
180 listHeight = m_maxPopupHeight - listTop - 1;
181
182 int listWidth = m_minPopupWidth;
183
184 for( size_t i = 0; i < m_listBox->GetCount(); ++i )
185 {
186 int itemWidth = KIUI::GetTextSize( m_listBox->GetString( i ), m_listBox ).x;
187 listWidth = std::max( listWidth, itemWidth + LIST_PADDING * 3 );
188 }
189
190 wxSize listSize( listWidth, listHeight );
191 wxSize popupSize( listWidth, listTop + listHeight );
192
193 SetSize( popupSize ); // us
194 GetParent()->SetSize( popupSize ); // the window that wxComboCtrl put us in
195
196 m_listBox->SetMinSize( listSize );
197 m_listBox->SetSize( listSize );
198
199 return popupSize;
200}
201
202
203void FILTER_COMBOPOPUP::onIdle( wxIdleEvent& aEvent )
204{
205 // Generate synthetic (but reliable) MouseMoved events
206 static wxPoint lastPos;
207 wxPoint screenPos = KIPLATFORM::UI::GetMousePosition();
208
209 if( screenPos != lastPos )
210 {
211 lastPos = screenPos;
212 onMouseMoved( screenPos );
213 }
214
215 if( m_focusHandler )
216 {
217 m_filterCtrl->PushEventHandler( m_focusHandler );
218 m_focusHandler = nullptr;
219 }
220}
221
222
223// Hot-track the mouse (for focus and listbox selection)
224void FILTER_COMBOPOPUP::onMouseMoved( const wxPoint aScreenPos )
225{
226 if( m_listBox->GetScreenRect().Contains( aScreenPos ) )
227 {
229
230 wxPoint relativePos = m_listBox->ScreenToClient( aScreenPos );
231 int item = m_listBox->HitTest( relativePos );
232
233 if( item >= 0 )
234 m_listBox->SetSelection( item );
235 }
236 else if( m_filterCtrl->GetScreenRect().Contains( aScreenPos ) )
237 {
239 }
240}
241
242
243void FILTER_COMBOPOPUP::onMouseClick( wxMouseEvent& aEvent )
244{
245 // Accept a click event from anywhere. Different platform implementations have
246 // different foibles with regard to transient popups and their children.
247 if( aEvent.GetEventObject() == m_listBox )
248 {
249 m_listBox->SetSelection( m_listBox->HitTest( aEvent.GetPosition() ) );
250 Accept();
251 return;
252 }
253
254 wxWindow* window = dynamic_cast<wxWindow*>( aEvent.GetEventObject() );
255
256 if( window )
257 {
258 wxPoint screenPos = window->ClientToScreen( aEvent.GetPosition() );
259
260 if( m_listBox->GetScreenRect().Contains( screenPos ) )
261 {
262 wxPoint localPos = m_listBox->ScreenToClient( screenPos );
263
264 m_listBox->SetSelection( m_listBox->HitTest( localPos ) );
265 Accept();
266 }
267 }
268}
269
270
271void FILTER_COMBOPOPUP::onKeyDown( wxKeyEvent& aEvent )
272{
273 switch( aEvent.GetKeyCode() )
274 {
275 // Control keys go to the parent combobox
276 case WXK_TAB:
277 Dismiss();
278
279 m_parent->NavigateIn( ( aEvent.ShiftDown() ? 0 : wxNavigationKeyEvent::IsForward ) |
280 ( aEvent.ControlDown() ? wxNavigationKeyEvent::WinChange : 0 ) );
281 break;
282
283 case WXK_ESCAPE:
284 Dismiss();
285 break;
286
287 case WXK_RETURN:
288 case WXK_NUMPAD_ENTER:
289 Accept();
290 break;
291
292 // Arrows go to the list box
293 case WXK_DOWN:
294 case WXK_NUMPAD_DOWN:
296 m_listBox->SetSelection( std::min( m_listBox->GetSelection() + 1,
297 (int) m_listBox->GetCount() - 1 ) );
298 break;
299
300 case WXK_UP:
301 case WXK_NUMPAD_UP:
303 m_listBox->SetSelection( std::max( m_listBox->GetSelection() - 1, 0 ) );
304 break;
305
306 // Everything else goes to the filter textbox
307 default:
308 if( !m_filterCtrl->HasFocus() )
309 {
311
312 // Because we didn't have focus we missed our chance to have the native widget
313 // handle the keystroke. We'll have to do the first character ourselves.
314 doStartingKey( aEvent );
315 }
316 else
317 {
318 // On some platforms a wxComboFocusHandler will have been pushed which
319 // unhelpfully gives the event right back to the popup. Make sure the filter
320 // control is going to get the event.
321 if( m_filterCtrl->GetEventHandler() != m_filterCtrl )
322 m_focusHandler = m_filterCtrl->PopEventHandler();
323
324 aEvent.Skip();
325 }
326 break;
327 }
328}
329
330
331void FILTER_COMBOPOPUP::onEnter( wxCommandEvent& aEvent )
332{
333 Accept();
334}
335
336
337void FILTER_COMBOPOPUP::onFilterEdit( wxCommandEvent& aEvent )
338{
339 rebuildList();
340 updateSize();
341
342 if( m_listBox->GetCount() > 0 )
343 m_listBox->SetSelection( 0 );
344}
345
346
347void FILTER_COMBOPOPUP::doStartingKey( wxKeyEvent& aEvent )
348{
349 if( aEvent.GetKeyCode() == WXK_BACK )
350 {
351 const long pos = m_filterCtrl->GetLastPosition();
352 m_filterCtrl->Remove( pos - 1, pos );
353 }
354 else
355 {
356 bool isPrintable;
357 int ch = aEvent.GetUnicodeKey();
358
359 if( ch != WXK_NONE )
360 isPrintable = true;
361 else
362 {
363 ch = aEvent.GetKeyCode();
364 isPrintable = ch > WXK_SPACE && ch < WXK_START;
365 }
366
367 if( isPrintable )
368 {
369 wxString text( static_cast<wxChar>( ch ) );
370
371 // wxCHAR_HOOK chars have been converted to uppercase.
372 if( !aEvent.ShiftDown() )
373 text.MakeLower();
374
375 m_filterCtrl->AppendText( text );
376 }
377 }
378}
379
380
381void FILTER_COMBOPOPUP::doSetFocus( wxWindow* aWindow )
382{
383#ifndef __WXGTK__ // Cannot focus in non-toplevel windows on GTK
385#endif
386}
387
388
389FILTER_COMBOBOX::FILTER_COMBOBOX( wxWindow *parent, wxWindowID id, const wxPoint &pos,
390 const wxSize &size, long style ) :
391 wxComboCtrl( parent, id, wxEmptyString, pos, size, style|wxCB_READONLY|wxTE_PROCESS_ENTER ),
392 m_filterPopup( nullptr )
393{
394 UseAltPopupWindow();
395 Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( FILTER_COMBOBOX::onKeyDown ), nullptr, this );
396
397 SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_LISTBOX ) );
398}
399
400
402{
403 Disconnect( wxEVT_CHAR_HOOK, wxKeyEventHandler( FILTER_COMBOBOX::onKeyDown ), nullptr, this );
404}
405
406
408{
409 m_filterPopup = aPopup;
410 SetPopupControl( aPopup );
411}
412
413
414void FILTER_COMBOBOX::onKeyDown( wxKeyEvent& aEvt )
415{
416 int key = aEvt.GetKeyCode();
417
418 if( IsPopupShown() )
419 {
420 // If the popup is shown then it's CHAR_HOOK should be eating these before they
421 // even get to us. But just to be safe, we go ahead and skip.
422 aEvt.Skip();
423 }
424 // Shift-return accepts dialog
425 else if( ( key == WXK_RETURN || key == WXK_NUMPAD_ENTER ) && aEvt.ShiftDown() )
426 {
427 wxPostEvent( m_parent, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
428 }
429 // Return, arrow-down and space-bar all open popup
430 else if( key == WXK_RETURN || key == WXK_NUMPAD_ENTER || key == WXK_DOWN
431 || key == WXK_NUMPAD_DOWN || key == WXK_SPACE )
432 {
433 Popup();
434 }
435 // Non-control characters go to filterbox in popup
436 else if( key > WXK_SPACE && key < WXK_START )
437 {
438 Popup();
440 }
441 else
442 {
443 aEvt.Skip();
444 }
445}
FILTER_COMBOPOPUP * m_filterPopup
void setFilterPopup(FILTER_COMBOPOPUP *aPopup)
FILTER_COMBOBOX(wxWindow *parent, wxWindowID id, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=0)
void onKeyDown(wxKeyEvent &aEvt)
void onEnter(wxCommandEvent &aEvent)
wxTextValidator * m_filterValidator
void onFilterEdit(wxCommandEvent &aEvent)
void SetStringValue(const wxString &aNetName) override
void onMouseClick(wxMouseEvent &aEvent)
void doStartingKey(wxKeyEvent &aEvent)
virtual void getListContent(wxArrayString &aListToFill)=0
Implement this to fill in the given list.
void rebuildList()
Call this to rebuild the list from the getListContent() method.
wxString getFilterValue() const
Get the current value of the filter control.
void OnStartingKey(wxKeyEvent &aEvent)
std::optional< wxString > getSelectedValue() const
Get the currently selected value in the list, or std::nullopt.
void onIdle(wxIdleEvent &aEvent)
wxSize GetAdjustedSize(int aMinWidth, int aPrefHeight, int aMaxHeight) override
wxListBox * m_listBox
wxEvtHandler * m_focusHandler
bool Create(wxWindow *aParent) override
void OnPopup() override
void doSetFocus(wxWindow *aWindow)
virtual void Accept()=0
void onKeyDown(wxKeyEvent &aEvent)
void onMouseMoved(const wxPoint aScreenPos)
wxTextCtrl * m_filterCtrl
#define _(s)
#define LIST_ITEM_PADDING
#define LIST_PADDING
wxDEFINE_EVENT(FILTERED_ITEM_SELECTED, wxCommandEvent)
wxPoint GetMousePosition()
Returns the mouse position in screen coordinates.
Definition: wxgtk/ui.cpp:677
void ForceFocus(wxWindow *aWindow)
Pass the current focus to the window.
Definition: wxgtk/ui.cpp:124
KICOMMON_API wxSize GetTextSize(const wxString &aSingleLine, wxWindow *aWindow)
Return the size of aSingleLine of text when it is rendered in aWindow using whatever font is currentl...
Definition: ui_common.cpp:77
Functions to provide common constants and other functions to assist in making a consistent UI.