KiCad PCB EDA Suite
net_selector.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 (C) 2018 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 
25 #include <widgets/net_selector.h>
26 
27 #include <class_board.h>
28 #include <netinfo.h>
29 #include <wx/arrstr.h>
30 #include <wx/display.h>
31 
32 wxDEFINE_EVENT( NET_SELECTED, wxCommandEvent );
33 
34 #if defined( __WXOSX_MAC__ )
35  #define POPUP_PADDING 2
36  #define LIST_ITEM_PADDING 5
37  #define LIST_PADDING 5
38 #elif defined( __WXMSW__ )
39  #define POPUP_PADDING 0
40  #define LIST_ITEM_PADDING 2
41  #define LIST_PADDING 5
42 #else
43  #define POPUP_PADDING 0
44  #define LIST_ITEM_PADDING 6
45  #define LIST_PADDING 5
46 #endif
47 
48 #define NO_NET _( "<no net>" )
49 
50 
51 class NET_SELECTOR_COMBOPOPUP : public wxPanel, public wxComboPopup
52 {
53 public:
55  m_filterValidator( nullptr ),
56  m_filterCtrl( nullptr ),
57  m_listBox( nullptr ),
58  m_minPopupWidth( -1 ),
59  m_maxPopupHeight( 1000 ),
60  m_netinfoList( nullptr ),
61  m_selectedNetcode( 0 ),
62  m_focusHandler( nullptr )
63  { }
64 
65  bool Create(wxWindow* aParent) override
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,
76  wxDefaultSize, 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, 0,
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( NET_SELECTOR_COMBOPOPUP::onIdle ), NULL, this );
90  Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR_COMBOPOPUP::onKeyDown ), NULL, this );
91  Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( NET_SELECTOR_COMBOPOPUP::onMouseClick ), NULL, this );
92  m_listBox->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( NET_SELECTOR_COMBOPOPUP::onMouseClick ), NULL, this );
93  m_filterCtrl->Connect( wxEVT_TEXT, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onFilterEdit ), NULL, this );
94  m_filterCtrl->Connect( wxEVT_TEXT_ENTER, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onEnter ), NULL, this );
95 
96  // <enter> in a ListBox comes in as a double-click on GTK
97  m_listBox->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onEnter ), NULL, this );
98 
99  return true;
100  }
101 
102  wxWindow *GetControl() override { return this; }
103 
104  void SetStringValue( const wxString& aNetName ) override
105  {
106  // shouldn't be here (combo is read-only)
107  }
108 
109  wxString GetStringValue() const override
110  {
112 
113  if( netInfo && netInfo->GetNet() > 0 )
114  return netInfo->GetNetname();
115 
116  return NO_NET;
117  }
118 
119  void SetNetInfo( NETINFO_LIST* aNetInfoList )
120  {
121  m_netinfoList = aNetInfoList;
122  rebuildList();
123  }
124 
126  bool IsIndeterminate() { return m_selectedNetcode == -1; }
127 
128  void SetSelectedNetcode( int aNetcode ) { m_selectedNetcode = aNetcode; }
130 
131  void SetSelectedNet( const wxString& aNetname )
132  {
133  if( m_netinfoList && m_netinfoList->GetNetItem( aNetname ) )
135  }
136 
138  {
141  else
142  return wxEmptyString;
143  }
144 
145  wxSize GetAdjustedSize( int aMinWidth, int aPrefHeight, int aMaxHeight ) override
146  {
147  // Called when the popup is first shown. Stash the minWidth and maxHeight so we
148  // can use them later when refreshing the sizes after filter changes.
149  m_minPopupWidth = aMinWidth;
150  m_maxPopupHeight = aMaxHeight;
151 
152  return updateSize();
153  }
154 
155  void OnPopup() override
156  {
157  // While it can sometimes be useful to keep the filter, it's always expected.
158  // Better to clear it.
159  m_filterCtrl->Clear();
160 
161  // The updateSize() call in GetAdjustedSize() leaves the height off-by-one for
162  // some reason, so do it again.
163  updateSize();
164  }
165 
166  void OnStartingKey( wxKeyEvent& aEvent )
167  {
169  doStartingKey( aEvent );
170  }
171 
172  void Accept()
173  {
174  wxString selectedNetName;
175  int selection = m_listBox->GetSelection();
176 
177  if( selection >= 0 )
178  selectedNetName = m_listBox->GetString( (unsigned) selection );
179 
180  if( selectedNetName.IsEmpty() )
181  {
182  m_selectedNetcode = -1;
183  GetComboCtrl()->SetValue( INDETERMINATE );
184  }
185  else if( selectedNetName == NO_NET )
186  {
187  m_selectedNetcode = 0;
188  GetComboCtrl()->SetValue( NO_NET );
189  }
190  else
191  {
192  NETINFO_ITEM* netInfo = m_netinfoList->GetNetItem( selectedNetName );
193 
194  if( netInfo == nullptr || netInfo->GetNet() == 0 )
195  {
196  m_selectedNetcode = 0;
197  GetComboCtrl()->SetValue( NO_NET );
198  }
199  else
200  {
201  m_selectedNetcode = netInfo->GetNet();
202  GetComboCtrl()->SetValue( selectedNetName );
203  }
204  }
205 
206  wxCommandEvent changeEvent( NET_SELECTED );
207  wxPostEvent( GetComboCtrl(), changeEvent );
208 
209  Dismiss();
210  }
211 
212 protected:
213  wxSize updateSize()
214  {
215  int listTop = m_listBox->GetRect().y;
216  int itemHeight = GetTextSize( wxT( "Xy" ), this ).y + LIST_ITEM_PADDING;
217  int listHeight = m_listBox->GetCount() * itemHeight + LIST_PADDING;
218 
219  if( listTop + listHeight >= m_maxPopupHeight )
220  listHeight = m_maxPopupHeight - listTop - 1;
221 
222  int listWidth = m_minPopupWidth;
223 
224  for( size_t i = 0; i < m_listBox->GetCount(); ++i )
225  {
226  int itemWidth = GetTextSize( m_listBox->GetString( i ), m_listBox ).x;
227  listWidth = std::max( listWidth, itemWidth + LIST_PADDING * 3 );
228  }
229 
230  wxSize listSize( listWidth, listHeight );
231  wxSize popupSize( listWidth, listTop + listHeight );
232 
233  SetSize( popupSize ); // us
234  GetParent()->SetSize( popupSize ); // the window that wxComboCtrl put us in
235 
236  m_listBox->SetMinSize( listSize );
237  m_listBox->SetSize( listSize );
238 
239  return popupSize;
240  }
241 
242  void rebuildList()
243  {
244  wxArrayString netNames;
245  wxString filter = m_filterCtrl->GetValue().MakeLower();
246 
247  if( !filter.IsEmpty() )
248  filter = wxT( "*" ) + filter + wxT( "*" );
249 
250  for( NETINFO_ITEM* netinfo : *m_netinfoList )
251  {
252  if( netinfo->GetNet() > 0 && netinfo->IsCurrent() )
253  {
254  wxString netname = UnescapeString( netinfo->GetNetname() );
255 
256  if( filter.IsEmpty() || wxString( netname ).MakeLower().Matches( filter ) )
257  netNames.push_back( netname );
258  }
259  }
260  std::sort( netNames.begin(), netNames.end() );
261 
262  // Special handling for <no net>
263  if( filter.IsEmpty() || wxString( NO_NET ).MakeLower().Matches( filter ) )
264  netNames.insert( netNames.begin(), NO_NET );
265 
266  m_listBox->Set( netNames );
267  }
268 
269  void onIdle( wxIdleEvent& aEvent )
270  {
271  // Generate synthetic (but reliable) MouseMoved events
272  static wxPoint lastPos;
273  wxPoint screenPos = wxGetMousePosition();
274 
275  if( screenPos != lastPos )
276  {
277  lastPos = screenPos;
278  onMouseMoved( screenPos );
279  }
280 
281  if( m_focusHandler )
282  {
283  m_filterCtrl->PushEventHandler( m_focusHandler );
284  m_focusHandler = nullptr;
285  }
286  }
287 
288  // Hot-track the mouse (for focus and listbox selection)
289  void onMouseMoved( const wxPoint aScreenPos )
290  {
291  if( m_listBox->GetScreenRect().Contains( aScreenPos ) )
292  {
294 
295  wxPoint relativePos = m_listBox->ScreenToClient( aScreenPos );
296  int item = m_listBox->HitTest( relativePos );
297 
298  if( item >= 0 )
299  m_listBox->SetSelection( item );
300  }
301  else if( m_filterCtrl->GetScreenRect().Contains( aScreenPos ) )
302  {
304  }
305  }
306 
307  void onMouseClick( wxMouseEvent& aEvent )
308  {
309  // Accept a click event from anywhere. Different platform implementations have
310  // different foibles with regard to transient popups and their children.
311 
312  if( aEvent.GetEventObject() == m_listBox )
313  {
314  m_listBox->SetSelection( m_listBox->HitTest( aEvent.GetPosition() ) );
315  Accept();
316  return;
317  }
318 
319  wxWindow* window = dynamic_cast<wxWindow*>( aEvent.GetEventObject() );
320 
321  if( window )
322  {
323  wxPoint screenPos = window->ClientToScreen( aEvent.GetPosition() );
324 
325  if( m_listBox->GetScreenRect().Contains( screenPos ) )
326  {
327  wxPoint localPos = m_listBox->ScreenToClient( screenPos );
328 
329  m_listBox->SetSelection( m_listBox->HitTest( localPos ) );
330  Accept();
331  }
332  }
333  }
334 
335  void onKeyDown( wxKeyEvent& aEvent )
336  {
337  switch( aEvent.GetKeyCode() )
338  {
339  // Control keys go to the parent combobox
340  case WXK_TAB:
341  Dismiss();
342 
343  m_parent->NavigateIn( ( aEvent.ShiftDown() ? 0 : wxNavigationKeyEvent::IsForward ) |
344  ( aEvent.ControlDown() ? wxNavigationKeyEvent::WinChange : 0 ) );
345  break;
346 
347  case WXK_ESCAPE:
348  Dismiss();
349  break;
350 
351  case WXK_RETURN:
352  Accept();
353  break;
354 
355  // Arrows go to the list box
356  case WXK_DOWN:
357  case WXK_NUMPAD_DOWN:
359  m_listBox->SetSelection( std::min( m_listBox->GetSelection() + 1, (int) m_listBox->GetCount() - 1 ) );
360  break;
361 
362  case WXK_UP:
363  case WXK_NUMPAD_UP:
365  m_listBox->SetSelection( std::max( m_listBox->GetSelection() - 1, 0 ) );
366  break;
367 
368  // Everything else goes to the filter textbox
369  default:
370  if( !m_filterCtrl->HasFocus() )
371  {
373 
374  // Because we didn't have focus we missed our chance to have the native widget
375  // handle the keystroke. We'll have to do the first character ourselves.
376  doStartingKey( aEvent );
377  }
378  else
379  {
380  // On some platforms a wxComboFocusHandler will have been pushed which
381  // unhelpfully gives the event right back to the popup. Make sure the filter
382  // control is going to get the event.
383  if( m_filterCtrl->GetEventHandler() != m_filterCtrl )
384  m_focusHandler = m_filterCtrl->PopEventHandler();
385 
386  aEvent.Skip();
387  }
388  break;
389  }
390  }
391 
392  void onEnter( wxCommandEvent& aEvent )
393  {
394  Accept();
395  }
396 
397  void onFilterEdit( wxCommandEvent& aEvent )
398  {
399  rebuildList();
400  updateSize();
401 
402  if( m_listBox->GetCount() > 0 )
403  m_listBox->SetSelection( 0 );
404  }
405 
406  void doStartingKey( wxKeyEvent& aEvent )
407  {
408  if( aEvent.GetKeyCode() == WXK_BACK )
409  {
410  const long pos = m_filterCtrl->GetLastPosition();
411  m_filterCtrl->Remove( pos - 1, pos );
412  }
413  else
414  {
415  bool isPrintable;
416  int ch = aEvent.GetUnicodeKey();
417 
418  if( ch != WXK_NONE )
419  isPrintable = true;
420  else
421  {
422  ch = aEvent.GetKeyCode();
423  isPrintable = ch > WXK_SPACE && ch < WXK_START;
424  }
425 
426  if( isPrintable )
427  {
428  wxString text( static_cast<wxChar>( ch ) );
429 
430  // wxCHAR_HOOK chars have been converted to uppercase.
431  if( !aEvent.ShiftDown() )
432  text.MakeLower();
433 
434  m_filterCtrl->AppendText( text );
435  }
436  }
437  }
438 
439  void doSetFocus( wxWindow* aWindow )
440  {
441 #ifdef __WXOSX_MAC__
442  aWindow->OSXForceFocus();
443 #else
444  aWindow->SetFocus();
445 #endif
446  }
447 
448 protected:
449  wxTextValidator* m_filterValidator;
450  wxTextCtrl* m_filterCtrl;
451  wxListBox* m_listBox;
454 
456 
458 
459  wxEvtHandler* m_focusHandler;
460 };
461 
462 
463 NET_SELECTOR::NET_SELECTOR( wxWindow *parent, wxWindowID id, const wxPoint &pos,
464  const wxSize &size, long style ) :
465  wxComboCtrl( parent, id, wxEmptyString, pos, size, style|wxCB_READONLY|wxTE_PROCESS_ENTER )
466 {
467  UseAltPopupWindow();
468 
470  SetPopupControl( m_netSelectorPopup );
471 
472  Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR::onKeyDown ), NULL, this );
473 }
474 
475 
477 {
478  Disconnect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR::onKeyDown ), NULL, this );
479 }
480 
481 
482 void NET_SELECTOR::onKeyDown( wxKeyEvent& aEvt )
483 {
484  int key = aEvt.GetKeyCode();
485 
486  if( IsPopupShown() )
487  {
488  // If the popup is shown then it's CHAR_HOOK should be eating these before they
489  // even get to us. But just to be safe, we go ahead and skip.
490  aEvt.Skip();
491  }
492 
493  // Shift-return accepts dialog
494  else if( key == WXK_RETURN && aEvt.ShiftDown() )
495  {
496  wxPostEvent( m_parent, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
497  }
498 
499  // Return, arrow-down and space-bar all open popup
500  else if( key == WXK_RETURN || key == WXK_DOWN || key == WXK_NUMPAD_DOWN || key == WXK_SPACE )
501  {
502  Popup();
503  }
504 
505  // Non-control characters go to filterbox in popup
506  else if( key > WXK_SPACE && key < WXK_START )
507  {
508  Popup();
510  }
511 
512  else
513  {
514  aEvt.Skip();
515  }
516 }
517 
518 
520 {
521  m_netSelectorPopup->SetNetInfo( aNetInfoList );
522 }
523 
524 
526 {
528  SetValue( m_netSelectorPopup->GetStringValue() );
529 }
530 
531 
532 void NET_SELECTOR::SetSelectedNet( const wxString& aNetname )
533 {
534  m_netSelectorPopup->SetSelectedNet( aNetname );
535  SetValue( m_netSelectorPopup->GetStringValue() );
536 }
537 
538 
540 {
542 }
543 
544 
546 {
548  SetValue( INDETERMINATE );
549 }
550 
551 
553 {
555 }
556 
557 
559 {
561 }
562 
void SetSelectedNet(const wxString &aNetname)
void onIdle(wxIdleEvent &aEvent)
void onFilterEdit(wxCommandEvent &aEvent)
wxTextValidator * m_filterValidator
wxString GetStringValue() const override
void SetNetInfo(NETINFO_LIST *aNetInfoList)
void SetSelectedNetcode(int aNetcode)
int GetSelectedNetcode()
wxEvtHandler * m_focusHandler
NET_SELECTOR(wxWindow *parent, wxWindowID id, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=0)
wxSize GetAdjustedSize(int aMinWidth, int aPrefHeight, int aMaxHeight) override
NETINFO_LIST * m_netinfoList
wxString GetSelectedNetname()
bool Create(wxWindow *aParent) override
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: common.cpp:113
wxWindow * GetControl() override
void onKeyDown(wxKeyEvent &aEvent)
#define NULL
wxDEFINE_EVENT(NET_SELECTED, wxCommandEvent)
void onKeyDown(wxKeyEvent &aEvt)
NETINFO_LIST is a container class for NETINFO_ITEM elements, which are the nets.
Definition: netinfo.h:409
#define INDETERMINATE
void SetSelectedNetcode(int aNetcode)
bool IsIndeterminate()
const wxString & GetNetname() const
Function GetNetname.
Definition: netinfo.h:233
#define LIST_ITEM_PADDING
void OnPopup() override
void SetNetInfo(NETINFO_LIST *aNetInfoList)
void SetStringValue(const wxString &aNetName) override
#define NO_NET
void doStartingKey(wxKeyEvent &aEvent)
NETINFO_ITEM handles the data for a net.
Definition: netinfo.h:65
void SetIndeterminate()
int GetNet() const
Function GetNet.
Definition: netinfo.h:225
void onEnter(wxCommandEvent &aEvent)
#define _(s)
Definition: 3d_actions.cpp:31
wxString UnescapeString(const wxString &aSource)
Definition: string.cpp:131
void SetSelectedNet(const wxString &aNetname)
~NET_SELECTOR() override
#define LIST_PADDING
void OnStartingKey(wxKeyEvent &aEvent)
void doSetFocus(wxWindow *aWindow)
NETINFO_ITEM * GetNetItem(int aNetCode) const
Function GetItem.
void onMouseClick(wxMouseEvent &aEvent)
NET_SELECTOR_COMBOPOPUP * m_netSelectorPopup
Definition: net_selector.h:63
void onMouseMoved(const wxPoint aScreenPos)