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