KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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#include <string_utils.h>
25#include <kiplatform/ui.h>
26
27#include <widgets/ui_common.h>
29
30#include <board.h>
31#include <netinfo.h>
32#include <wx/arrstr.h>
33#include <wx/display.h>
34#include <wx/valtext.h>
35#include <wx/listbox.h>
36#include <wx/stattext.h>
37#include <wx/sizer.h>
38#include <wx/textctrl.h>
39#include <wx/panel.h>
40
41
42wxDEFINE_EVENT( NET_SELECTED, wxCommandEvent );
43
44#if defined( __WXOSX_MAC__ )
45 #define POPUP_PADDING 2
46 #define LIST_ITEM_PADDING 5
47 #define LIST_PADDING 5
48#elif defined( __WXMSW__ )
49 #define POPUP_PADDING 0
50 #define LIST_ITEM_PADDING 2
51 #define LIST_PADDING 5
52#else
53 #define POPUP_PADDING 0
54 #define LIST_ITEM_PADDING 6
55 #define LIST_PADDING 5
56#endif
57
58#define NO_NET _( "<no net>" )
59#define CREATE_NET _( "<create net>" )
60
61
62class NET_SELECTOR_COMBOPOPUP : public wxPanel, public wxComboPopup
63{
64public:
66 m_filterValidator( nullptr ),
67 m_filterCtrl( nullptr ),
68 m_listBox( nullptr ),
69 m_minPopupWidth( -1 ),
70 m_maxPopupHeight( 1000 ),
71 m_netinfoList( nullptr ),
72 m_board( nullptr ),
74 m_focusHandler( nullptr )
75 { }
76
77 bool Create(wxWindow* aParent) override
78 {
79 wxPanel::Create( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER );
80
81 wxBoxSizer* mainSizer;
82 mainSizer = new wxBoxSizer( wxVERTICAL );
83
84 wxStaticText* filterLabel = new wxStaticText( this, wxID_ANY, _( "Filter:" ) );
85 mainSizer->Add( filterLabel, 0, wxEXPAND, 0 );
86
87 m_filterCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition,
88 wxDefaultSize, wxTE_PROCESS_ENTER );
89 m_filterValidator = new wxTextValidator( wxFILTER_EXCLUDE_CHAR_LIST );
90 m_filterValidator->SetCharExcludes( " " );
91 m_filterCtrl->SetValidator( *m_filterValidator );
92 mainSizer->Add( m_filterCtrl, 0, wxEXPAND, 0 );
93
94 m_listBox = new wxListBox( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr,
95 wxLB_SINGLE|wxLB_NEEDED_SB );
96 mainSizer->Add( m_listBox, 0, wxEXPAND|wxTOP, 2 );
97
98 SetSizer( mainSizer );
99 Layout();
100
101 Connect( wxEVT_IDLE, wxIdleEventHandler( NET_SELECTOR_COMBOPOPUP::onIdle ), nullptr, this );
102 Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR_COMBOPOPUP::onKeyDown ), nullptr, this );
103 Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( NET_SELECTOR_COMBOPOPUP::onMouseClick ), nullptr, this );
104 m_listBox->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( NET_SELECTOR_COMBOPOPUP::onMouseClick ), nullptr, this );
105 m_filterCtrl->Connect( wxEVT_TEXT, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onFilterEdit ), nullptr, this );
106 m_filterCtrl->Connect( wxEVT_TEXT_ENTER, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onEnter ), nullptr, this );
107
108 // <enter> in a ListBox comes in as a double-click on GTK
109 m_listBox->Connect( wxEVT_COMMAND_LISTBOX_DOUBLECLICKED, wxCommandEventHandler( NET_SELECTOR_COMBOPOPUP::onEnter ), nullptr, this );
110
111 return true;
112 }
113
114 wxWindow *GetControl() override { return this; }
115
116 void SetStringValue( const wxString& aNetName ) override
117 {
118 // shouldn't be here (combo is read-only)
119 }
120
121 wxString GetStringValue() const override
122 {
123 if( m_selectedNetcode == -1 )
125
127
128 if( netInfo && netInfo->GetNetCode() > 0 )
129 return netInfo->GetNetname();
130
131 return NO_NET;
132 }
133
134 void SetNetInfo( const NETINFO_LIST* aNetInfoList )
135 {
136 m_netinfoList = aNetInfoList;
137 rebuildList();
138 }
139
140 void SetIndeterminateLabel( const wxString& aIndeterminateLabel )
141 {
142 m_indeterminateLabel = aIndeterminateLabel;
143 rebuildList();
144 }
145
146 void SetBoard( BOARD* aBoard )
147 {
148 m_board = aBoard;
149 }
150
152 bool IsIndeterminate() { return m_selectedNetcode == -1; }
153
154 void SetSelectedNetcode( int aNetcode ) { m_selectedNetcode = aNetcode; }
156
157 void SetSelectedNet( const wxString& aNetname )
158 {
159 if( m_netinfoList && m_netinfoList->GetNetItem( aNetname ) )
161 }
162
164 {
167 else
168 return wxEmptyString;
169 }
170
171 wxSize GetAdjustedSize( int aMinWidth, int aPrefHeight, int aMaxHeight ) override
172 {
173 // Called when the popup is first shown. Stash the minWidth and maxHeight so we
174 // can use them later when refreshing the sizes after filter changes.
175 m_minPopupWidth = aMinWidth;
176 m_maxPopupHeight = aMaxHeight;
177
178 return updateSize();
179 }
180
181 void OnPopup() override
182 {
183 // While it can sometimes be useful to keep the filter, it's always unexpected.
184 // Better to clear it.
185 m_filterCtrl->Clear();
186
187 m_listBox->SetStringSelection( GetStringValue() );
188 m_filterCtrl->SetFocus();
189
190 // The updateSize() call in GetAdjustedSize() leaves the height off-by-one for
191 // some reason, so do it again.
192 updateSize();
193 }
194
195 void OnStartingKey( wxKeyEvent& aEvent )
196 {
198 doStartingKey( aEvent );
199 }
200
201 void Accept()
202 {
203 wxString selectedNetName;
204 wxString escapedNetName;
205 wxString remainingName;
206 int selection = m_listBox->GetSelection();
207
208 if( selection >= 0 )
209 selectedNetName = m_listBox->GetString( (unsigned) selection );
210
211 auto it = m_unescapedNetNameMap.find( selectedNetName );
212
213 if( it != m_unescapedNetNameMap.end() )
214 escapedNetName = it->second;
215 else // shouldn't happen....
216 escapedNetName = selectedNetName;
217
218 Dismiss();
219
220 if( escapedNetName.IsEmpty() || escapedNetName == m_indeterminateLabel )
221 {
223 GetComboCtrl()->SetValue( m_indeterminateLabel );
224 }
225 else if( escapedNetName == NO_NET )
226 {
228 GetComboCtrl()->SetValue( NO_NET );
229 }
230 else if( escapedNetName.StartsWith( CREATE_NET, &remainingName ) &&
231 !remainingName.IsEmpty() )
232 {
233 // Remove the first character ':' and all whitespace
234 remainingName = remainingName.Mid( 1 ).Trim().Trim( false );
235
236 BOARD* board = m_netinfoList->GetParent();
237 NETINFO_ITEM *newnet = new NETINFO_ITEM( m_board, remainingName, 0 );
238
239 wxASSERT( board );
240
241 if( board )
242 board->Add( newnet );
243
244 rebuildList();
245
246 if( newnet->GetNetCode() > 0 )
247 {
248 m_selectedNetcode = newnet->GetNetCode();
249 GetComboCtrl()->SetValue( UnescapeString( remainingName ) );
250 }
251 else
252 {
253 // This indicates that the NETINFO_ITEM was not successfully appended to the
254 // list for unknown reasons
255 if( board )
256 board->Remove( newnet );
257
258 delete newnet;
259 }
260 }
261 else
262 {
263 NETINFO_ITEM* netInfo = m_netinfoList->GetNetItem( escapedNetName );
264
265 if( netInfo == nullptr || netInfo->GetNetCode() == 0 )
266 {
268 GetComboCtrl()->SetValue( NO_NET );
269 }
270 else
271 {
272 m_selectedNetcode = netInfo->GetNetCode();
273 GetComboCtrl()->SetValue( UnescapeString( escapedNetName ) );
274 }
275 }
276
277 wxCommandEvent changeEvent( NET_SELECTED );
278 wxPostEvent( GetComboCtrl(), changeEvent );
279 }
280
281protected:
282 wxSize updateSize()
283 {
284 int listTop = m_listBox->GetRect().y;
285 int itemHeight = KIUI::GetTextSize( wxT( "Xy" ), this ).y + LIST_ITEM_PADDING;
286 int listHeight = m_listBox->GetCount() * itemHeight + LIST_PADDING;
287
288 if( listTop + listHeight >= m_maxPopupHeight )
289 listHeight = m_maxPopupHeight - listTop - 1;
290
291 int listWidth = m_minPopupWidth;
292
293 for( size_t i = 0; i < m_listBox->GetCount(); ++i )
294 {
295 int itemWidth = KIUI::GetTextSize( m_listBox->GetString( i ), m_listBox ).x;
296 listWidth = std::max( listWidth, itemWidth + LIST_PADDING * 3 );
297 }
298
299 wxSize listSize( listWidth, listHeight );
300 wxSize popupSize( listWidth, listTop + listHeight );
301
302 SetSize( popupSize ); // us
303 GetParent()->SetSize( popupSize ); // the window that wxComboCtrl put us in
304
305 m_listBox->SetMinSize( listSize );
306 m_listBox->SetSize( listSize );
307
308 return popupSize;
309 }
310
312 {
313 wxArrayString netNames;
314 wxString netstring = m_filterCtrl->GetValue().Trim().Trim( false );
315 wxString filter = netstring.Lower();
316
317 m_unescapedNetNameMap.clear();
318
319 if( !filter.IsEmpty() )
320 filter = wxT( "*" ) + filter + wxT( "*" );
321
322 for( NETINFO_ITEM* netinfo : *m_netinfoList )
323 {
324 if( netinfo->GetNetCode() > 0 && netinfo->IsCurrent() )
325 {
326 wxString netname = UnescapeString( netinfo->GetNetname() );
327
328 if( filter.IsEmpty() || wxString( netname ).MakeLower().Matches( filter ) )
329 {
330 netNames.push_back( netname );
331 m_unescapedNetNameMap[ netname ] = netinfo->GetNetname();
332 }
333 }
334 }
335
336 std::sort( netNames.begin(), netNames.end(),
337 []( const wxString& lhs, const wxString& rhs )
338 {
339 return StrNumCmp( lhs, rhs, true /* ignore case */ ) < 0;
340 } );
341
342
343 // Special handling for <no net>
344 if( filter.IsEmpty() || wxString( NO_NET ).MakeLower().Matches( filter ) )
345 netNames.insert( netNames.begin(), NO_NET );
346
347 if( !filter.IsEmpty() && !m_netinfoList->GetNetItem( netstring ) )
348 {
349 wxString newnet = wxString::Format( "%s: %s", CREATE_NET, netstring );
350 netNames.insert( netNames.end(), newnet );
351 }
352
353 if( !m_indeterminateLabel.IsEmpty() )
354 netNames.push_back( m_indeterminateLabel );
355
356 m_listBox->Set( netNames );
357 }
358
359 void onIdle( wxIdleEvent& aEvent )
360 {
361 // Generate synthetic (but reliable) MouseMoved events
362 static wxPoint lastPos;
363 wxPoint screenPos = KIPLATFORM::UI::GetMousePosition();
364
365 if( screenPos != lastPos )
366 {
367 lastPos = screenPos;
368 onMouseMoved( screenPos );
369 }
370
371 if( m_focusHandler )
372 {
373 m_filterCtrl->PushEventHandler( m_focusHandler );
374 m_focusHandler = nullptr;
375 }
376 }
377
378 // Hot-track the mouse (for focus and listbox selection)
379 void onMouseMoved( const wxPoint aScreenPos )
380 {
381 if( m_listBox->GetScreenRect().Contains( aScreenPos ) )
382 {
384
385 wxPoint relativePos = m_listBox->ScreenToClient( aScreenPos );
386 int item = m_listBox->HitTest( relativePos );
387
388 if( item >= 0 )
389 m_listBox->SetSelection( item );
390 }
391 else if( m_filterCtrl->GetScreenRect().Contains( aScreenPos ) )
392 {
394 }
395 }
396
397 void onMouseClick( wxMouseEvent& aEvent )
398 {
399 // Accept a click event from anywhere. Different platform implementations have
400 // different foibles with regard to transient popups and their children.
401
402 if( aEvent.GetEventObject() == m_listBox )
403 {
404 m_listBox->SetSelection( m_listBox->HitTest( aEvent.GetPosition() ) );
405 Accept();
406 return;
407 }
408
409 wxWindow* window = dynamic_cast<wxWindow*>( aEvent.GetEventObject() );
410
411 if( window )
412 {
413 wxPoint screenPos = window->ClientToScreen( aEvent.GetPosition() );
414
415 if( m_listBox->GetScreenRect().Contains( screenPos ) )
416 {
417 wxPoint localPos = m_listBox->ScreenToClient( screenPos );
418
419 m_listBox->SetSelection( m_listBox->HitTest( localPos ) );
420 Accept();
421 }
422 }
423 }
424
425 void onKeyDown( wxKeyEvent& aEvent )
426 {
427 switch( aEvent.GetKeyCode() )
428 {
429 // Control keys go to the parent combobox
430 case WXK_TAB:
431 Dismiss();
432
433 m_parent->NavigateIn( ( aEvent.ShiftDown() ? 0 : wxNavigationKeyEvent::IsForward ) |
434 ( aEvent.ControlDown() ? wxNavigationKeyEvent::WinChange : 0 ) );
435 break;
436
437 case WXK_ESCAPE:
438 Dismiss();
439 break;
440
441 case WXK_RETURN:
442 case WXK_NUMPAD_ENTER:
443 Accept();
444 break;
445
446 // Arrows go to the list box
447 case WXK_DOWN:
448 case WXK_NUMPAD_DOWN:
450 m_listBox->SetSelection( std::min( m_listBox->GetSelection() + 1, (int) m_listBox->GetCount() - 1 ) );
451 break;
452
453 case WXK_UP:
454 case WXK_NUMPAD_UP:
456 m_listBox->SetSelection( std::max( m_listBox->GetSelection() - 1, 0 ) );
457 break;
458
459 // Everything else goes to the filter textbox
460 default:
461 if( !m_filterCtrl->HasFocus() )
462 {
464
465 // Because we didn't have focus we missed our chance to have the native widget
466 // handle the keystroke. We'll have to do the first character ourselves.
467 doStartingKey( aEvent );
468 }
469 else
470 {
471 // On some platforms a wxComboFocusHandler will have been pushed which
472 // unhelpfully gives the event right back to the popup. Make sure the filter
473 // control is going to get the event.
474 if( m_filterCtrl->GetEventHandler() != m_filterCtrl )
475 m_focusHandler = m_filterCtrl->PopEventHandler();
476
477 aEvent.Skip();
478 }
479 break;
480 }
481 }
482
483 void onEnter( wxCommandEvent& aEvent )
484 {
485 Accept();
486 }
487
488 void onFilterEdit( wxCommandEvent& aEvent )
489 {
490 rebuildList();
491 updateSize();
492
493 if( m_listBox->GetCount() > 0 )
494 m_listBox->SetSelection( 0 );
495 }
496
497 void doStartingKey( wxKeyEvent& aEvent )
498 {
499 if( aEvent.GetKeyCode() == WXK_BACK )
500 {
501 const long pos = m_filterCtrl->GetLastPosition();
502 m_filterCtrl->Remove( pos - 1, pos );
503 }
504 else
505 {
506 bool isPrintable;
507 int ch = aEvent.GetUnicodeKey();
508
509 if( ch != WXK_NONE )
510 isPrintable = true;
511 else
512 {
513 ch = aEvent.GetKeyCode();
514 isPrintable = ch > WXK_SPACE && ch < WXK_START;
515 }
516
517 if( isPrintable )
518 {
519 wxString text( static_cast<wxChar>( ch ) );
520
521 // wxCHAR_HOOK chars have been converted to uppercase.
522 if( !aEvent.ShiftDown() )
523 text.MakeLower();
524
525 m_filterCtrl->AppendText( text );
526 }
527 }
528 }
529
530 void doSetFocus( wxWindow* aWindow )
531 {
532#ifndef __WXGTK__ // Cannot focus in non-toplevel windows on GTK
534#endif
535 }
536
537protected:
538 wxTextValidator* m_filterValidator;
539 wxTextCtrl* m_filterCtrl;
540 wxListBox* m_listBox;
543
547
549
550 std::map<wxString, wxString> m_unescapedNetNameMap;
551
552 wxEvtHandler* m_focusHandler;
553};
554
555
556NET_SELECTOR::NET_SELECTOR( wxWindow *parent, wxWindowID id, const wxPoint &pos,
557 const wxSize &size, long style ) :
558 wxComboCtrl( parent, id, wxEmptyString, pos, size, style|wxCB_READONLY|wxTE_PROCESS_ENTER )
559{
560 UseAltPopupWindow();
561
563 SetPopupControl( m_netSelectorPopup );
564
565 Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR::onKeyDown ), nullptr, this );
566}
567
568
570{
571 Disconnect( wxEVT_CHAR_HOOK, wxKeyEventHandler( NET_SELECTOR::onKeyDown ), nullptr, this );
572}
573
574
575void NET_SELECTOR::onKeyDown( wxKeyEvent& aEvt )
576{
577 int key = aEvt.GetKeyCode();
578
579 if( IsPopupShown() )
580 {
581 // If the popup is shown then it's CHAR_HOOK should be eating these before they
582 // even get to us. But just to be safe, we go ahead and skip.
583 aEvt.Skip();
584 }
585
586 // Shift-return accepts dialog
587 else if( ( key == WXK_RETURN || key == WXK_NUMPAD_ENTER ) && aEvt.ShiftDown() )
588 {
589 wxPostEvent( m_parent, wxCommandEvent( wxEVT_COMMAND_BUTTON_CLICKED, wxID_OK ) );
590 }
591
592 // Return, arrow-down and space-bar all open popup
593 else if( key == WXK_RETURN || key == WXK_NUMPAD_ENTER || key == WXK_DOWN
594 || key == WXK_NUMPAD_DOWN || key == WXK_SPACE )
595 {
596 Popup();
597 }
598
599 // Non-control characters go to filterbox in popup
600 else if( key > WXK_SPACE && key < WXK_START )
601 {
602 Popup();
604 }
605
606 else
607 {
608 aEvt.Skip();
609 }
610}
611
612
613void NET_SELECTOR::SetNetInfo( const NETINFO_LIST* aNetInfoList )
614{
615 m_netSelectorPopup->SetNetInfo( aNetInfoList );
616}
617
618
619void NET_SELECTOR::SetIndeterminateString( const wxString& aString )
620{
621 m_indeterminateString = aString;
623}
624
625
627{
628 m_netSelectorPopup->SetBoard( aBoard );
629}
630
631
633{
636}
637
638
639void NET_SELECTOR::SetSelectedNet( const wxString& aNetname )
640{
643}
644
645
647{
649}
650
651
653{
655 SetValue( m_indeterminateString );
656}
657
658
660{
662}
663
664
666{
668}
669
Information pertinent to a Pcbnew printed circuit board.
Definition: board.h:281
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition: board.cpp:882
void Remove(BOARD_ITEM *aBoardItem, REMOVE_MODE aMode=REMOVE_MODE::NORMAL) override
Removes an item from the container.
Definition: board.cpp:1020
Handle the data for a net.
Definition: netinfo.h:56
const wxString & GetNetname() const
Definition: netinfo.h:114
int GetNetCode() const
Definition: netinfo.h:108
Container for NETINFO_ITEM elements, which are the nets.
Definition: netinfo.h:342
BOARD * GetParent() const
Definition: netinfo.h:457
NETINFO_ITEM * GetNetItem(int aNetCode) const
void onEnter(wxCommandEvent &aEvent)
void SetStringValue(const wxString &aNetName) override
void doSetFocus(wxWindow *aWindow)
void SetSelectedNet(const wxString &aNetname)
void onMouseMoved(const wxPoint aScreenPos)
void onMouseClick(wxMouseEvent &aEvent)
wxEvtHandler * m_focusHandler
wxWindow * GetControl() override
const NETINFO_LIST * m_netinfoList
std::map< wxString, wxString > m_unescapedNetNameMap
void SetIndeterminateLabel(const wxString &aIndeterminateLabel)
wxString GetStringValue() const override
void onKeyDown(wxKeyEvent &aEvent)
void SetSelectedNetcode(int aNetcode)
bool Create(wxWindow *aParent) override
void onFilterEdit(wxCommandEvent &aEvent)
void OnPopup() override
wxTextValidator * m_filterValidator
void SetNetInfo(const NETINFO_LIST *aNetInfoList)
void SetBoard(BOARD *aBoard)
void onIdle(wxIdleEvent &aEvent)
void OnStartingKey(wxKeyEvent &aEvent)
void doStartingKey(wxKeyEvent &aEvent)
wxSize GetAdjustedSize(int aMinWidth, int aPrefHeight, int aMaxHeight) override
void SetNetInfo(const NETINFO_LIST *aNetInfoList)
wxString m_indeterminateString
Definition: net_selector.h:68
NET_SELECTOR_COMBOPOPUP * m_netSelectorPopup
Definition: net_selector.h:67
NET_SELECTOR(wxWindow *parent, wxWindowID id, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=0)
bool IsIndeterminate()
void SetBoard(BOARD *aBoard)
int GetSelectedNetcode()
void SetIndeterminateString(const wxString &aString)
void SetSelectedNetcode(int aNetcode)
void onKeyDown(wxKeyEvent &aEvt)
void SetIndeterminate()
void SetSelectedNet(const wxString &aNetname)
~NET_SELECTOR() override
wxString GetSelectedNetname()
#define _(s)
wxPoint GetMousePosition()
Returns the mouse position in screen coordinates.
Definition: gtk/ui.cpp:601
void ForceFocus(wxWindow *aWindow)
Pass the current focus to the window.
Definition: gtk/ui.cpp:67
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:74
wxDEFINE_EVENT(NET_SELECTED, wxCommandEvent)
#define LIST_ITEM_PADDING
#define LIST_PADDING
#define CREATE_NET
#define NO_NET
wxString UnescapeString(const wxString &aSource)
Functions to provide common constants and other functions to assist in making a consistent UI.