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 #define CREATE_NET _( "<create net>" )
50 
51 
52 class NET_SELECTOR_COMBOPOPUP : public wxPanel, public wxComboPopup
53 {
54 public:
56  m_filterValidator( nullptr ),
57  m_filterCtrl( nullptr ),
58  m_listBox( nullptr ),
59  m_minPopupWidth( -1 ),
60  m_maxPopupHeight( 1000 ),
61  m_netinfoList( nullptr ),
62  m_board( nullptr ),
63  m_selectedNetcode( 0 ),
64  m_focusHandler( nullptr )
65  { }
66 
67  bool Create(wxWindow* aParent) override
68  {
69  wxPanel::Create( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER );
70 
71  wxBoxSizer* mainSizer;
72  mainSizer = new wxBoxSizer( wxVERTICAL );
73 
74  wxStaticText* filterLabel = new wxStaticText( this, wxID_ANY, _( "Filter:" ) );
75  mainSizer->Add( filterLabel, 0, wxEXPAND, 0 );
76 
77  m_filterCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition,
78  wxDefaultSize, wxTE_PROCESS_ENTER );
79  m_filterValidator = new wxTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
80  m_filterValidator->SetCharExcludes( " " );
81  m_filterCtrl->SetValidator( *m_filterValidator );
82  mainSizer->Add( m_filterCtrl, 0, wxEXPAND, 0 );
83 
84  m_listBox = new wxListBox( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, 0,
85  wxLB_SINGLE|wxLB_NEEDED_SB );
86  mainSizer->Add( m_listBox, 0, wxEXPAND|wxTOP, 2 );
87 
88  SetSizer( mainSizer );
89  Layout();
90 
91  Connect( wxEVT_IDLE, wxIdleEventHandler( NET_SELECTOR_COMBOPOPUP::onIdle ), NULL, this );
92  Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR_COMBOPOPUP::onKeyDown ), NULL, this );
93  Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( NET_SELECTOR_COMBOPOPUP::onMouseClick ), NULL, this );
94  m_listBox->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( NET_SELECTOR_COMBOPOPUP::onMouseClick ), NULL, this );
95  m_filterCtrl->Connect( wxEVT_TEXT, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onFilterEdit ), NULL, this );
96  m_filterCtrl->Connect( wxEVT_TEXT_ENTER, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onEnter ), NULL, this );
97 
98  // <enter> in a ListBox comes in as a double-click on GTK
99  m_listBox->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onEnter ), NULL, this );
100 
101  return true;
102  }
103 
104  wxWindow *GetControl() override { return this; }
105 
106  void SetStringValue( const wxString& aNetName ) override
107  {
108  // shouldn't be here (combo is read-only)
109  }
110 
111  wxString GetStringValue() const override
112  {
114 
115  if( netInfo && netInfo->GetNet() > 0 )
116  return netInfo->GetNetname();
117 
118  return NO_NET;
119  }
120 
121  void SetNetInfo( NETINFO_LIST* aNetInfoList )
122  {
123  m_netinfoList = aNetInfoList;
124  rebuildList();
125  }
126 
127  void SetBoard( BOARD* aBoard )
128  {
129  m_board = aBoard;
130  }
131 
133  bool IsIndeterminate() { return m_selectedNetcode == -1; }
134 
135  void SetSelectedNetcode( int aNetcode ) { m_selectedNetcode = aNetcode; }
137 
138  void SetSelectedNet( const wxString& aNetname )
139  {
140  if( m_netinfoList && m_netinfoList->GetNetItem( aNetname ) )
142  }
143 
145  {
148  else
149  return wxEmptyString;
150  }
151 
152  wxSize GetAdjustedSize( int aMinWidth, int aPrefHeight, int aMaxHeight ) override
153  {
154  // Called when the popup is first shown. Stash the minWidth and maxHeight so we
155  // can use them later when refreshing the sizes after filter changes.
156  m_minPopupWidth = aMinWidth;
157  m_maxPopupHeight = aMaxHeight;
158 
159  return updateSize();
160  }
161 
162  void OnPopup() override
163  {
164  // While it can sometimes be useful to keep the filter, it's always expected.
165  // Better to clear it.
166  m_filterCtrl->Clear();
167 
168  // The updateSize() call in GetAdjustedSize() leaves the height off-by-one for
169  // some reason, so do it again.
170  updateSize();
171  }
172 
173  void OnStartingKey( wxKeyEvent& aEvent )
174  {
176  doStartingKey( aEvent );
177  }
178 
179  void Accept()
180  {
181  wxString selectedNetName;
182  wxString remainingName;
183  int selection = m_listBox->GetSelection();
184  wxString prefix = CREATE_NET;
185 
186  if( selection >= 0 )
187  selectedNetName = m_listBox->GetString( (unsigned) selection );
188 
189  if( selectedNetName.IsEmpty() )
190  {
191  m_selectedNetcode = -1;
192  GetComboCtrl()->SetValue( INDETERMINATE );
193  }
194  else if( selectedNetName == NO_NET )
195  {
196  m_selectedNetcode = 0;
197  GetComboCtrl()->SetValue( NO_NET );
198  }
199  else if( selectedNetName.StartsWith( CREATE_NET, &remainingName ) &&
200  !remainingName.IsEmpty() )
201  {
202  // Remove the first character ':' and all whitespace
203  remainingName = remainingName.Mid( 1 ).Trim().Trim( false );
204  NETINFO_ITEM *newnet = new NETINFO_ITEM( m_board, remainingName, 0 );
205 
206  m_netinfoList->AppendNet( newnet );
207  rebuildList();
208 
209  if( newnet->GetNet() > 0 )
210  {
211  m_selectedNetcode = newnet->GetNet();
212  GetComboCtrl()->SetValue( remainingName );
213  }
214  else
215  {
216  // This indicates that the NETINFO_ITEM was not successfully appended
217  // to the list for unknown reasons
218  delete newnet;
219  }
220  }
221  else
222  {
223  NETINFO_ITEM* netInfo = m_netinfoList->GetNetItem( selectedNetName );
224 
225  if( netInfo == nullptr || netInfo->GetNet() == 0 )
226  {
227  m_selectedNetcode = 0;
228  GetComboCtrl()->SetValue( NO_NET );
229  }
230  else
231  {
232  m_selectedNetcode = netInfo->GetNet();
233  GetComboCtrl()->SetValue( selectedNetName );
234  }
235  }
236 
237  wxCommandEvent changeEvent( NET_SELECTED );
238  wxPostEvent( GetComboCtrl(), changeEvent );
239 
240  Dismiss();
241  }
242 
243 protected:
244  wxSize updateSize()
245  {
246  int listTop = m_listBox->GetRect().y;
247  int itemHeight = GetTextSize( wxT( "Xy" ), this ).y + LIST_ITEM_PADDING;
248  int listHeight = m_listBox->GetCount() * itemHeight + LIST_PADDING;
249 
250  if( listTop + listHeight >= m_maxPopupHeight )
251  listHeight = m_maxPopupHeight - listTop - 1;
252 
253  int listWidth = m_minPopupWidth;
254 
255  for( size_t i = 0; i < m_listBox->GetCount(); ++i )
256  {
257  int itemWidth = GetTextSize( m_listBox->GetString( i ), m_listBox ).x;
258  listWidth = std::max( listWidth, itemWidth + LIST_PADDING * 3 );
259  }
260 
261  wxSize listSize( listWidth, listHeight );
262  wxSize popupSize( listWidth, listTop + listHeight );
263 
264  SetSize( popupSize ); // us
265  GetParent()->SetSize( popupSize ); // the window that wxComboCtrl put us in
266 
267  m_listBox->SetMinSize( listSize );
268  m_listBox->SetSize( listSize );
269 
270  return popupSize;
271  }
272 
273  void rebuildList()
274  {
275  wxArrayString netNames;
276  wxString netstring = m_filterCtrl->GetValue().MakeLower();
277  wxString filter = netstring;
278 
279  if( !filter.IsEmpty() )
280  filter = wxT( "*" ) + filter + wxT( "*" );
281 
282  for( NETINFO_ITEM* netinfo : *m_netinfoList )
283  {
284  if( netinfo->GetNet() > 0 && netinfo->IsCurrent() )
285  {
286  wxString netname = UnescapeString( netinfo->GetNetname() );
287 
288  if( filter.IsEmpty() || wxString( netname ).MakeLower().Matches( filter ) )
289  netNames.push_back( netname );
290  }
291  }
292  std::sort( netNames.begin(), netNames.end() );
293 
294  // Special handling for <no net>
295  if( filter.IsEmpty() || wxString( NO_NET ).MakeLower().Matches( filter ) )
296  netNames.insert( netNames.begin(), NO_NET );
297 
298  if( !filter.IsEmpty() && netNames.IsEmpty() )
299  {
300  wxString newnet = wxString::Format( "%s: %s", CREATE_NET, netstring );
301  netNames.insert( netNames.begin(), newnet );
302  }
303 
304  m_listBox->Set( netNames );
305  }
306 
307  void onIdle( wxIdleEvent& aEvent )
308  {
309  // Generate synthetic (but reliable) MouseMoved events
310  static wxPoint lastPos;
311  wxPoint screenPos = wxGetMousePosition();
312 
313  if( screenPos != lastPos )
314  {
315  lastPos = screenPos;
316  onMouseMoved( screenPos );
317  }
318 
319  if( m_focusHandler )
320  {
321  m_filterCtrl->PushEventHandler( m_focusHandler );
322  m_focusHandler = nullptr;
323  }
324  }
325 
326  // Hot-track the mouse (for focus and listbox selection)
327  void onMouseMoved( const wxPoint aScreenPos )
328  {
329  if( m_listBox->GetScreenRect().Contains( aScreenPos ) )
330  {
332 
333  wxPoint relativePos = m_listBox->ScreenToClient( aScreenPos );
334  int item = m_listBox->HitTest( relativePos );
335 
336  if( item >= 0 )
337  m_listBox->SetSelection( item );
338  }
339  else if( m_filterCtrl->GetScreenRect().Contains( aScreenPos ) )
340  {
342  }
343  }
344 
345  void onMouseClick( wxMouseEvent& aEvent )
346  {
347  // Accept a click event from anywhere. Different platform implementations have
348  // different foibles with regard to transient popups and their children.
349 
350  if( aEvent.GetEventObject() == m_listBox )
351  {
352  m_listBox->SetSelection( m_listBox->HitTest( aEvent.GetPosition() ) );
353  Accept();
354  return;
355  }
356 
357  wxWindow* window = dynamic_cast<wxWindow*>( aEvent.GetEventObject() );
358 
359  if( window )
360  {
361  wxPoint screenPos = window->ClientToScreen( aEvent.GetPosition() );
362 
363  if( m_listBox->GetScreenRect().Contains( screenPos ) )
364  {
365  wxPoint localPos = m_listBox->ScreenToClient( screenPos );
366 
367  m_listBox->SetSelection( m_listBox->HitTest( localPos ) );
368  Accept();
369  }
370  }
371  }
372 
373  void onKeyDown( wxKeyEvent& aEvent )
374  {
375  switch( aEvent.GetKeyCode() )
376  {
377  // Control keys go to the parent combobox
378  case WXK_TAB:
379  Dismiss();
380 
381  m_parent->NavigateIn( ( aEvent.ShiftDown() ? 0 : wxNavigationKeyEvent::IsForward ) |
382  ( aEvent.ControlDown() ? wxNavigationKeyEvent::WinChange : 0 ) );
383  break;
384 
385  case WXK_ESCAPE:
386  Dismiss();
387  break;
388 
389  case WXK_RETURN:
390  Accept();
391  break;
392 
393  // Arrows go to the list box
394  case WXK_DOWN:
395  case WXK_NUMPAD_DOWN:
397  m_listBox->SetSelection( std::min( m_listBox->GetSelection() + 1, (int) m_listBox->GetCount() - 1 ) );
398  break;
399 
400  case WXK_UP:
401  case WXK_NUMPAD_UP:
403  m_listBox->SetSelection( std::max( m_listBox->GetSelection() - 1, 0 ) );
404  break;
405 
406  // Everything else goes to the filter textbox
407  default:
408  if( !m_filterCtrl->HasFocus() )
409  {
411 
412  // Because we didn't have focus we missed our chance to have the native widget
413  // handle the keystroke. We'll have to do the first character ourselves.
414  doStartingKey( aEvent );
415  }
416  else
417  {
418  // On some platforms a wxComboFocusHandler will have been pushed which
419  // unhelpfully gives the event right back to the popup. Make sure the filter
420  // control is going to get the event.
421  if( m_filterCtrl->GetEventHandler() != m_filterCtrl )
422  m_focusHandler = m_filterCtrl->PopEventHandler();
423 
424  aEvent.Skip();
425  }
426  break;
427  }
428  }
429 
430  void onEnter( wxCommandEvent& aEvent )
431  {
432  Accept();
433  }
434 
435  void onFilterEdit( wxCommandEvent& aEvent )
436  {
437  rebuildList();
438  updateSize();
439 
440  if( m_listBox->GetCount() > 0 )
441  m_listBox->SetSelection( 0 );
442  }
443 
444  void doStartingKey( wxKeyEvent& aEvent )
445  {
446  if( aEvent.GetKeyCode() == WXK_BACK )
447  {
448  const long pos = m_filterCtrl->GetLastPosition();
449  m_filterCtrl->Remove( pos - 1, pos );
450  }
451  else
452  {
453  bool isPrintable;
454  int ch = aEvent.GetUnicodeKey();
455 
456  if( ch != WXK_NONE )
457  isPrintable = true;
458  else
459  {
460  ch = aEvent.GetKeyCode();
461  isPrintable = ch > WXK_SPACE && ch < WXK_START;
462  }
463 
464  if( isPrintable )
465  {
466  wxString text( static_cast<wxChar>( ch ) );
467 
468  // wxCHAR_HOOK chars have been converted to uppercase.
469  if( !aEvent.ShiftDown() )
470  text.MakeLower();
471 
472  m_filterCtrl->AppendText( text );
473  }
474  }
475  }
476 
477  void doSetFocus( wxWindow* aWindow )
478  {
479 #ifdef __WXOSX_MAC__
480  aWindow->OSXForceFocus();
481 #else
482  aWindow->SetFocus();
483 #endif
484  }
485 
486 protected:
487  wxTextValidator* m_filterValidator;
488  wxTextCtrl* m_filterCtrl;
489  wxListBox* m_listBox;
492 
495 
497 
498  wxEvtHandler* m_focusHandler;
499 };
500 
501 
502 NET_SELECTOR::NET_SELECTOR( wxWindow *parent, wxWindowID id, const wxPoint &pos,
503  const wxSize &size, long style ) :
504  wxComboCtrl( parent, id, wxEmptyString, pos, size, style|wxCB_READONLY|wxTE_PROCESS_ENTER )
505 {
506  UseAltPopupWindow();
507 
509  SetPopupControl( m_netSelectorPopup );
510 
511  Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR::onKeyDown ), NULL, this );
512 }
513 
514 
516 {
517  Disconnect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR::onKeyDown ), NULL, this );
518 }
519 
520 
521 void NET_SELECTOR::onKeyDown( wxKeyEvent& aEvt )
522 {
523  int key = aEvt.GetKeyCode();
524 
525  if( IsPopupShown() )
526  {
527  // If the popup is shown then it's CHAR_HOOK should be eating these before they
528  // even get to us. But just to be safe, we go ahead and skip.
529  aEvt.Skip();
530  }
531 
532  // Shift-return accepts dialog
533  else if( key == WXK_RETURN && aEvt.ShiftDown() )
534  {
535  wxPostEvent( m_parent, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
536  }
537 
538  // Return, arrow-down and space-bar all open popup
539  else if( key == WXK_RETURN || key == WXK_DOWN || key == WXK_NUMPAD_DOWN || key == WXK_SPACE )
540  {
541  Popup();
542  }
543 
544  // Non-control characters go to filterbox in popup
545  else if( key > WXK_SPACE && key < WXK_START )
546  {
547  Popup();
549  }
550 
551  else
552  {
553  aEvt.Skip();
554  }
555 }
556 
557 
559 {
560  m_netSelectorPopup->SetNetInfo( aNetInfoList );
561 }
562 
563 
565 {
566  m_netSelectorPopup->SetBoard( aBoard );
567 }
568 
569 
571 {
573  SetValue( m_netSelectorPopup->GetStringValue() );
574 }
575 
576 
577 void NET_SELECTOR::SetSelectedNet( const wxString& aNetname )
578 {
579  m_netSelectorPopup->SetSelectedNet( aNetname );
580  SetValue( m_netSelectorPopup->GetStringValue() );
581 }
582 
583 
585 {
587 }
588 
589 
591 {
593  SetValue( INDETERMINATE );
594 }
595 
596 
598 {
600 }
601 
602 
604 {
606 }
607 
#define CREATE_NET
void SetSelectedNet(const wxString &aNetname)
void onIdle(wxIdleEvent &aEvent)
void onFilterEdit(wxCommandEvent &aEvent)
wxTextValidator * m_filterValidator
void SetBoard(BOARD *aBoard)
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:263
void SetBoard(BOARD *aBoard)
void AppendNet(NETINFO_ITEM *aNewElement)
Function AppendNet adds aNewElement to the end of the net list.
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:408
#define INDETERMINATE
void SetSelectedNetcode(int aNetcode)
bool IsIndeterminate()
const wxString & GetNetname() const
Function GetNetname.
Definition: netinfo.h:232
#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()
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:205
int GetNet() const
Function GetNet.
Definition: netinfo.h:224
BOARD holds information pertinent to a Pcbnew printed circuit board.
Definition: class_board.h:163
void onEnter(wxCommandEvent &aEvent)
#define _(s)
Definition: 3d_actions.cpp:33
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:65
void onMouseMoved(const wxPoint aScreenPos)