KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_net_inspector_panel_data_model.h
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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * 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, see <https://www.gnu.org/licenses/>.
18 */
19
20#ifndef PCB_NET_INSPECTOR_PANEL_DATA_MODEL
21#define PCB_NET_INSPECTOR_PANEL_DATA_MODEL
22
24
25#include <wx/settings.h>
26
27#include <eda_pattern_match.h>
28#include <string_utils.h>
29#include <board.h>
32
33
40{
41public:
49
50 LIST_ITEM( unsigned int aGroupNumber, const wxString& aGroupName, GROUP_TYPE aGroupType ) :
51 m_group_type( aGroupType ),
52 m_group_number( aGroupNumber ),
53 m_net_name( aGroupName )
54 {
55 m_group_name = aGroupName;
56 m_column_changed.resize( COLUMN_LAST_STATIC_COL + 1 + 2, 0 ); // 2 for default layer count
57 }
58
61 m_net( aNet )
62 {
63 wxASSERT( aNet );
67 m_column_changed.resize( COLUMN_LAST_STATIC_COL + 1 + 2, 0 );
68 }
69
71 {
72 m_column_changed.resize( COLUMN_LAST_STATIC_COL + 1 + 2, 0 );
73 }
74
75 LIST_ITEM& operator=( const LIST_ITEM& ) = delete;
76
77 bool GetIsGroup() const { return m_group_type != GROUP_TYPE::NONE; }
78 const wxString& GetGroupName() const { return m_group_name; }
80 int GetGroupNumber() const { return m_group_number; }
81
82 auto ChildrenBegin() const { return m_children.begin(); }
83 auto ChildrenEnd() const { return m_children.end(); }
84 unsigned int ChildrenCount() const { return m_children.size(); }
85
86 void SetLayerCount( unsigned int aValue )
87 {
88 m_column_changed.resize( COLUMN_LAST_STATIC_COL + 1 + aValue, 0 );
89 }
90
91 NETINFO_ITEM* GetNet() const { return m_net; }
92
93 int GetNetCode() const
94 {
95 return GetIsGroup() ? ( 0 - int( m_group_number ) - 1 ) : m_net->GetNetCode();
96 }
97
98 const wxString& GetNetName() const { return m_net_name; }
99 const wxString& GetNetclassName() const { return m_net_class; }
100 const wxString& GetNetChainName() const { return m_netChainName; }
101
103 {
104 std::fill( m_column_changed.begin(), m_column_changed.end(), 0 );
105 }
106
107 unsigned int GetPadCount() const { return m_pad_count; }
108
110
111 void SetPadCount( unsigned int aValue )
112 {
113 if( m_parent )
114 m_parent->SetPadCount( m_parent->GetPadCount() - m_pad_count + aValue );
115
117 m_pad_count = aValue;
118 }
119
121
122 void SetNetChainName( const wxString& aValue )
123 {
125 m_netChainName = aValue;
126 }
127
128 void AddPadCount( unsigned int aValue )
129 {
130 if( m_parent )
131 m_parent->AddPadCount( aValue );
132
133 m_column_changed[COLUMN_PAD_COUNT] |= ( aValue != 0 );
134 m_pad_count += aValue;
135 }
136
137 void SubPadCount( unsigned int aValue )
138 {
139 if( m_parent )
140 m_parent->SubPadCount( aValue );
141
142 m_column_changed[COLUMN_PAD_COUNT] |= ( aValue != 0 );
143 m_pad_count -= aValue;
144 }
145
146 unsigned GetViaCount() const { return m_via_count; }
147
149
150 void SetViaCount( unsigned int aValue )
151 {
152 if( m_parent )
153 m_parent->SetViaCount( m_parent->GetViaCount() - m_via_count + aValue );
154
156 m_via_count = aValue;
157 }
158
159 void AddViaCount( unsigned int aValue )
160 {
161 if( m_parent )
162 m_parent->AddViaCount( aValue );
163
164 m_column_changed[COLUMN_VIA_COUNT] |= ( aValue != 0 );
165 m_via_count += aValue;
166 }
167
168 void SubViaCount( unsigned int aValue )
169 {
170 if( m_parent )
171 m_parent->SubViaCount( aValue );
172
173 m_column_changed[COLUMN_VIA_COUNT] |= ( aValue != 0 );
174 m_via_count -= aValue;
175 }
176
177 int64_t GetViaLength() const { return m_via_length; }
178
180
181 void SetViaLength( unsigned int aValue )
182 {
183 if( m_parent )
184 m_parent->SetViaLength( m_parent->GetViaLength() - m_via_length + aValue );
185
187 m_via_length = aValue;
188 }
189
190 void AddViaLength( unsigned int aValue )
191 {
192 if( m_parent )
193 m_parent->AddViaLength( aValue );
194
195 m_column_changed[COLUMN_VIA_LENGTH] |= ( aValue != 0 );
196 m_via_length += aValue;
197 }
198
199 void SubViaLength( int64_t aValue )
200 {
201 if( m_parent )
202 m_parent->SubViaLength( aValue );
203
204 m_column_changed[COLUMN_VIA_LENGTH] |= ( aValue != 0 );
205 m_via_length -= aValue;
206 }
207
208 int64_t GetViaDelay() const { return m_via_delay; }
209
210 void SetViaDelay( unsigned int aValue )
211 {
212 if( m_parent )
213 m_parent->SetViaDelay( m_parent->GetViaDelay() - m_via_delay + aValue );
214
216 m_via_delay = aValue;
217 }
218
219 void AddViaDelay( unsigned int aValue )
220 {
221 if( m_parent )
222 m_parent->AddViaDelay( aValue );
223
224 m_column_changed[COLUMN_VIA_LENGTH] |= ( aValue != 0 );
225 m_via_delay += aValue;
226 }
227
228 void SubViaDelay( int64_t aValue )
229 {
230 if( m_parent )
231 m_parent->SubViaDelay( aValue );
232
233 m_column_changed[COLUMN_VIA_LENGTH] |= ( aValue != 0 );
234 m_via_delay -= aValue;
235 }
236
237 int64_t GetBoardWireLength() const
238 {
239 int64_t retval = 0;
240
241 for( auto& [layer, length] : m_layer_wire_length )
242 retval += length;
243
244 return retval;
245 }
246
247 int64_t GetBoardWireDelay() const
248 {
249 int64_t retval = 0;
250
251 for( auto& [layer, delay] : m_layer_wire_delay )
252 retval += delay;
253
254 return retval;
255 }
256
257 int64_t GetLayerWireLength( PCB_LAYER_ID aLayer ) const
258 {
259 auto it = m_layer_wire_length.find( aLayer );
260 return it != m_layer_wire_length.end() ? it->second : 0;
261 }
262
263 int64_t GetLayerWireDelay( PCB_LAYER_ID aLayer ) const
264 {
265 auto it = m_layer_wire_delay.find( aLayer );
266 return it != m_layer_wire_delay.end() ? it->second : 0;
267 }
268
270
271 void SetLayerWireLength( const int64_t aValue, PCB_LAYER_ID aLayer )
272 {
273 auto it = m_layer_wire_length.find( aLayer );
274
275 wxCHECK_RET( it != m_layer_wire_length.end(), wxT( "Invalid layer specified" ) );
276
277 auto& [_, length] = *it;
278
279 if( m_parent )
280 {
281 m_parent->SetLayerWireLength( m_parent->GetBoardWireLength() - length + aValue,
282 aLayer );
283 }
284
285 m_column_changed[COLUMN_BOARD_LENGTH] |= ( length != aValue );
286 length = aValue;
287 }
288
289 std::map<PCB_LAYER_ID, int64_t> GetLayerWireLengths() const { return m_layer_wire_length; }
290
291 std::map<PCB_LAYER_ID, int64_t> GetLayerWireDelays() const { return m_layer_wire_delay; }
292
293 void SetLayerWireLengths( const std::map<PCB_LAYER_ID, int64_t>& aValue )
294 {
295 if( m_parent )
296 {
297 for( auto& [oldLayer, oldLength] : m_layer_wire_length )
298 m_parent->SubLayerWireLength( oldLength, oldLayer );
299
300 for( auto& [newLayer, newLength] : aValue )
301 m_parent->AddLayerWireLength( newLength, newLayer );
302 }
303
304 m_layer_wire_length = aValue;
305 }
306
307 void AddLayerWireLength( const int64_t aValue, PCB_LAYER_ID aLayer )
308 {
309 if( m_parent )
310 m_parent->AddLayerWireLength( aValue, aLayer );
311
313 m_layer_wire_length[aLayer] += aValue;
314 }
315
316 void SubLayerWireLength( const int64_t aValue, PCB_LAYER_ID aLayer )
317 {
318 if( m_parent )
319 m_parent->SubLayerWireLength( aValue, aLayer );
320
322 m_layer_wire_length[aLayer] -= aValue;
323 }
324
325 void SetLayerWireDelays( const std::map<PCB_LAYER_ID, int64_t>& aValue )
326 {
327 if( m_parent )
328 {
329 for( auto& [oldLayer, oldLength] : m_layer_wire_delay )
330 m_parent->SubLayerWireDelay( oldLength, oldLayer );
331
332 for( auto& [newLayer, newLength] : aValue )
333 m_parent->AddLayerWireDelay( newLength, newLayer );
334 }
335
336 m_layer_wire_delay = aValue;
337 }
338
339 void AddLayerWireDelay( const int64_t aValue, PCB_LAYER_ID aLayer )
340 {
341 if( m_parent )
342 m_parent->AddLayerWireDelay( aValue, aLayer );
343
345 m_layer_wire_delay[aLayer] += aValue;
346 }
347
348 void SubLayerWireDelay( const int64_t aValue, PCB_LAYER_ID aLayer )
349 {
350 if( m_parent )
351 m_parent->SubLayerWireDelay( aValue, aLayer );
352
354 m_layer_wire_delay[aLayer] -= aValue;
355 }
356
357 int64_t GetPadDieLength() const { return m_pad_die_length; }
358
360
361 void SetPadDieLength( int64_t aValue )
362 {
363 if( m_parent )
364 m_parent->SetPadDieLength( m_parent->GetPadDieLength() - m_pad_die_length + aValue );
365
367 m_pad_die_length = aValue;
368 }
369
370 void AddPadDieLength( int64_t aValue )
371 {
372 if( m_parent )
373 m_parent->AddPadDieLength( aValue );
374
375 m_column_changed[COLUMN_PAD_DIE_LENGTH] |= ( aValue != 0 );
376 m_pad_die_length += aValue;
377 }
378
379 void SubPadDieLength( int64_t aValue )
380 {
381 if( m_parent )
382 m_parent->SubPadDieLength( aValue );
383
384 m_column_changed[COLUMN_PAD_DIE_LENGTH] |= ( aValue != 0 );
385 m_pad_die_length -= aValue;
386 }
387
388 int64_t GetNetChainLength() const { return m_netChainLength; }
389
391
392 void SetNetChainLength( int64_t aValue )
393 {
395 m_netChainLength = aValue;
396 }
397
398 int64_t GetNetChainDelay() const { return m_netChainDelay; }
399
400 void SetNetChainDelay( int64_t aValue )
401 {
403 m_netChainDelay = aValue;
404 }
405
406 int64_t GetPadDieDelay() const { return m_pad_die_delay; }
407
408 void SetPadDieDelay( int64_t aValue )
409 {
410 if( m_parent )
411 m_parent->SetPadDieDelay( m_parent->GetPadDieDelay() - m_pad_die_delay + aValue );
412
414 m_pad_die_delay = aValue;
415 }
416
417 void AddPadDieDelay( int64_t aValue )
418 {
419 if( m_parent )
420 m_parent->AddPadDieDelay( aValue );
421
422 m_column_changed[COLUMN_PAD_DIE_LENGTH] |= ( aValue != 0 );
423 m_pad_die_delay += aValue;
424 }
425
426 void SubPadDieDelay( int64_t aValue )
427 {
428 if( m_parent )
429 m_parent->SubPadDieDelay( aValue );
430
431 m_column_changed[COLUMN_PAD_DIE_LENGTH] |= ( aValue != 0 );
432 m_pad_die_delay -= aValue;
433 }
434
435 int64_t GetUnroutedLength() const { return m_unrouted_length; }
436
438
439 void SetUnroutedLength( int64_t aValue )
440 {
441 if( m_parent )
442 m_parent->SetUnroutedLength( m_parent->GetUnroutedLength() - m_unrouted_length + aValue );
443
445 m_unrouted_length = aValue;
446 }
447
448 void AddUnroutedLength( int64_t aValue )
449 {
450 if( m_parent )
451 m_parent->AddUnroutedLength( aValue );
452
453 m_column_changed[COLUMN_UNROUTED_LENGTH] |= ( aValue != 0 );
454 m_unrouted_length += aValue;
455 }
456
457 void SubUnroutedLength( int64_t aValue )
458 {
459 if( m_parent )
460 m_parent->SubUnroutedLength( aValue );
461
462 m_column_changed[COLUMN_UNROUTED_LENGTH] |= ( aValue != 0 );
463 m_unrouted_length -= aValue;
464 }
465
466 // the total length column is always computed, never stored.
467 unsigned long long int GetTotalLength() const
468 {
470 }
471
472 unsigned long long int GetTotalDelay() const { return GetBoardWireDelay() + GetViaDelay() + GetPadDieDelay(); }
473
475 {
477 }
478
479 LIST_ITEM* Parent() const { return m_parent; }
480
481 void SetParent( LIST_ITEM* aParent )
482 {
483 if( m_parent == aParent )
484 return;
485
486 if( m_parent != nullptr )
487 {
488 m_parent->SubPadCount( GetPadCount() );
489 m_parent->SubViaCount( GetViaCount() );
490 m_parent->SubViaLength( GetViaLength() );
491 m_parent->SubViaDelay( GetViaDelay() );
492
493 for( auto& [layer, length] : m_layer_wire_length )
494 m_parent->SubLayerWireLength( length, layer );
495
496 for( auto& [layer, delay] : m_layer_wire_delay )
497 m_parent->SubLayerWireDelay( delay, layer );
498
499 m_parent->SubPadDieLength( GetPadDieLength() );
500 m_parent->SubPadDieDelay( GetPadDieDelay() );
501 m_parent->SubUnroutedLength( GetUnroutedLength() );
502
503 m_parent->m_children.erase( std::find( m_parent->m_children.begin(),
504 m_parent->m_children.end(), this ) );
505 }
506
507 m_parent = aParent;
508
509 if( m_parent != nullptr )
510 {
511 m_parent->AddPadCount( GetPadCount() );
512 m_parent->AddViaCount( GetViaCount() );
513 m_parent->AddViaLength( GetViaLength() );
514 m_parent->AddViaDelay( GetViaDelay() );
515
516 for( auto& [layer, length] : m_layer_wire_length )
517 m_parent->AddLayerWireLength( length, layer );
518
519 for( auto& [layer, delay] : m_layer_wire_delay )
520 m_parent->AddLayerWireDelay( delay, layer );
521
522 m_parent->AddPadDieLength( GetPadDieLength() );
523 m_parent->AddPadDieDelay( GetPadDieDelay() );
524 m_parent->AddUnroutedLength( GetUnroutedLength() );
525
526 m_parent->m_children.push_back( this );
527 }
528 }
529
530private:
531 LIST_ITEM* m_parent = nullptr;
532 std::vector<LIST_ITEM*> m_children;
533
535 unsigned int m_group_number = 0;
536 NETINFO_ITEM* m_net = nullptr;
537 unsigned int m_pad_count = 0;
538 unsigned int m_via_count = 0;
539 int64_t m_via_length = 0;
540 int64_t m_via_delay = 0;
541 int64_t m_pad_die_length = 0;
542 int64_t m_pad_die_delay = 0;
543 int64_t m_netChainLength = 0;
544 int64_t m_netChainDelay = 0;
545 int64_t m_unrouted_length = 0;
546
547 std::map<PCB_LAYER_ID, int64_t> m_layer_wire_length{};
548 std::map<PCB_LAYER_ID, int64_t> m_layer_wire_delay{};
549
550 // Dirty bits to record when some attribute has changed, in order to avoid unnecessary sort
551 // operations.
552 // The values are semantically bools, but STL auto-promotes a std::vector<bool> to a bitset,
553 // and then operator|= doesn't work.
554 std::vector<int> m_column_changed;
555
556 // cached formatted names for faster display sorting
557 wxString m_net_name;
558 wxString m_net_class;
560 wxString m_group_name;
561};
562
563
565{
566 template <typename T>
567 bool operator()( const T& a, const T& b ) const
568 {
569 return a->GetNetCode() < b->GetNetCode();
570 }
571
572 template <typename T>
573 bool operator()( const T& a, int b ) const
574 {
575 return a->GetNetCode() < b;
576 }
577
578 template <typename T>
579 bool operator()( int a, const T& b ) const
580 {
581 return a < b->GetNetCode();
582 }
583};
584
585
587{
588 template <typename T>
589 bool operator()( const T& a, const T& b ) const
590 {
591 return a->GetGroupNumber() < b->GetGroupNumber();
592 }
593
594 template <typename T>
595 bool operator()( const T& a, int b ) const
596 {
597 return a->GetGroupNumber() < b;
598 }
599
600 template <typename T>
601 bool operator()( int a, const T& b ) const
602 {
603 return a < b->GetGroupNumber();
604 }
605};
606
607
611class PCB_NET_INSPECTOR_PANEL::DATA_MODEL : public wxDataViewModel
612{
613public:
615 m_parent( parent )
616 {}
617
618 unsigned int columnCount() const { return m_parent.m_columns.size(); }
619
620 unsigned int itemCount() const { return m_items.size(); }
621
622 wxVariant valueAt( unsigned int aCol, unsigned int aRow ) const
623 {
624 wxVariant r;
625 GetValue( r, wxDataViewItem( const_cast<LIST_ITEM*>( &*( m_items[aRow] ) ) ), aCol );
626 return r;
627 }
628
629 const LIST_ITEM& itemAt( unsigned int aRow ) const { return *m_items.at( aRow ); }
630
631 wxVariant valueForItem( const LIST_ITEM* aItem, unsigned int aCol ) const
632 {
633 wxVariant r;
634 GetValue( r, wxDataViewItem( const_cast<LIST_ITEM*>( aItem ) ), aCol );
635 return r;
636 }
637
638 std::vector<std::pair<wxString, wxDataViewItem>> getGroupDataViewItems()
639 {
640 std::vector<std::pair<wxString, wxDataViewItem>> ret;
641
642 for( std::unique_ptr<LIST_ITEM>& item : m_items )
643 {
644 if( item->GetIsGroup() )
645 ret.push_back( std::make_pair( item->GetGroupName(), wxDataViewItem( item.get() ) ) );
646 }
647
648 return ret;
649 }
650
651 std::optional<LIST_ITEM_ITER> findItem( int aNetCode )
652 {
653 auto i = std::lower_bound( m_items.begin(), m_items.end(), aNetCode,
655
656 if( i == m_items.end() || ( *i )->GetNetCode() != aNetCode )
657 return std::nullopt;
658
659 return { i };
660 }
661
662 std::optional<LIST_ITEM_ITER> findItem( NETINFO_ITEM* aNet )
663 {
664 if( aNet != nullptr )
665 return findItem( aNet->GetNetCode() );
666 else
667 return std::nullopt;
668 }
669
670 std::optional<LIST_ITEM_ITER> findGroupItem( int aGroupNumber )
671 {
672 auto i = std::lower_bound( m_items.begin(), m_items.end(), aGroupNumber,
674
675 if( i == m_items.end() || ( *i )->GetGroupNumber() != aGroupNumber )
676 return std::nullopt;
677
678 return { i };
679 }
680
682 wxString groupName, LIST_ITEM::GROUP_TYPE groupType )
683 {
684 LIST_ITEM_ITER group = std::find_if( groupsBegin, groupsEnd,
685 [&]( const std::unique_ptr<LIST_ITEM>& x )
686 {
687 return x->GetGroupName() == groupName
688 && x->GetGroupType() == groupType;
689 } );
690
691 if( group == groupsEnd )
692 {
693 int dist = std::distance( groupsBegin, groupsEnd );
694 std::unique_ptr<LIST_ITEM> groupItem = std::make_unique<LIST_ITEM>( dist, groupName, groupType );
695
696 group = m_items.insert( groupsEnd, std::move( groupItem ) );
697 ItemAdded( wxDataViewItem( ( *group )->Parent() ), wxDataViewItem( ( *group ).get() ) );
698 }
699
700 return ( *group ).get();
701 }
702
703 std::optional<LIST_ITEM_ITER> addItem( std::unique_ptr<LIST_ITEM> aItem )
704 {
705 if( aItem == nullptr )
706 return {};
707
708 bool groupMatched = false;
709
710 // First see if item matches a group-by rule
711 if( m_parent.m_custom_group_rules.size() > 0 )
712 {
713 wxString searchName = aItem->GetNetName();
714
715 for( const std::unique_ptr<EDA_COMBINED_MATCHER>& rule : m_parent.m_custom_group_rules )
716 {
717 if( rule->Find( searchName ) )
718 {
719 aItem->SetParent( m_custom_group_map[ rule->GetPattern() ] );
720 groupMatched = true;
721 break;
722 }
723 }
724 }
725
726 // Then add any chain groups required by this item
727 if( m_parent.m_groupByNetChain && !groupMatched )
728 {
729 LIST_ITEM_ITER groups_begin = m_items.begin();
730 LIST_ITEM_ITER groups_end = std::find_if_not( m_items.begin(), m_items.end(),
731 []( const std::unique_ptr<LIST_ITEM>& x )
732 {
733 return x->GetIsGroup();
734 } );
735
736 wxString match_str = aItem->GetNetChainName();
737 LIST_ITEM* group = addGroup( groups_begin, groups_end, match_str,
739 aItem->SetParent( group );
740 groupMatched = true;
741 }
742
743 // Then add any netclass groups required by this item
744 if( m_parent.m_groupByNetclass && !groupMatched )
745 {
746 LIST_ITEM_ITER groups_begin = m_items.begin();
747 LIST_ITEM_ITER groups_end = std::find_if_not( m_items.begin(), m_items.end(),
748 []( const std::unique_ptr<LIST_ITEM>& x )
749 {
750 return x->GetIsGroup();
751 } );
752
753 wxString match_str = aItem->GetNetclassName();
754 LIST_ITEM* group = addGroup( groups_begin, groups_end, match_str, LIST_ITEM::GROUP_TYPE::NETCLASS );
755 aItem->SetParent( group );
756 groupMatched = true;
757 }
758
759 // Now add the item itself. Usually when new nets are added,
760 // they always get a higher netcode number than the already existing ones.
761 // however, if we've got filtering enabled, we might not have all the nets in
762 // our list, so do a sorted insertion.
763 auto new_iter = std::lower_bound( m_items.begin(), m_items.end(), aItem->GetNetCode(),
765
766 new_iter = m_items.insert( new_iter, std::move( aItem ) );
767 const std::unique_ptr<LIST_ITEM>& new_item = *new_iter;
768
769 ItemAdded( wxDataViewItem( new_item->Parent() ), wxDataViewItem( new_item.get() ) );
770
771 return { new_iter };
772 }
773
774 void addItems( std::vector<std::unique_ptr<LIST_ITEM>>& aItems )
775 {
776 m_items.reserve( m_items.size() + aItems.size() );
777
778 for( std::unique_ptr<LIST_ITEM>& i : aItems )
779 addItem( std::move( i ) );
780 }
781
782 std::unique_ptr<LIST_ITEM> deleteItem( const std::optional<LIST_ITEM_ITER>& aRow )
783 {
784 if( !aRow )
785 return {};
786
787 std::unique_ptr<LIST_ITEM> i = std::move( **aRow );
788
789 LIST_ITEM* parent = i->Parent();
790 i->SetParent( nullptr );
791
792 m_items.erase( *aRow );
793 ItemDeleted( wxDataViewItem( parent ), wxDataViewItem( &*i ) );
794
795 if( parent )
796 {
797 ItemChanged( wxDataViewItem( parent ) );
798
799 if( parent != nullptr && parent->ChildrenCount() == 0
800 && ( ( m_parent.m_groupByNetclass && parent->GetGroupType() == LIST_ITEM::GROUP_TYPE::NETCLASS )
801 || ( m_parent.m_groupByNetChain && parent->GetGroupType() == LIST_ITEM::GROUP_TYPE::NET_CHAIN ) ) )
802 {
803 auto p = std::find_if( m_items.begin(), m_items.end(),
804 [&]( std::unique_ptr<LIST_ITEM>& x )
805 {
806 return x.get() == parent;
807 } );
808
809 wxASSERT( p != m_items.end() );
810
811 // Keep the parent alive through the ItemDeleted notification; erasing it from
812 // m_items destroys the LIST_ITEM, after which parent would be dangling.
813 wxDataViewItem grandParent( parent->Parent() );
814 std::unique_ptr<LIST_ITEM> removedParent = std::move( *p );
815 m_items.erase( p );
816
817 ItemDeleted( grandParent, wxDataViewItem( removedParent.get() ) );
818 }
819 }
820
821 Resort();
822 return i;
823 }
824
831 {
832 m_custom_group_map.clear();
833 int groupId = 0;
834
835 for( const std::unique_ptr<EDA_COMBINED_MATCHER>& rule : m_parent.m_custom_group_rules )
836 {
837 std::unique_ptr<LIST_ITEM>& group = m_items.emplace_back( std::make_unique<LIST_ITEM>(
838 groupId, rule->GetPattern(), LIST_ITEM::GROUP_TYPE::USER_DEFINED ) );
839 m_custom_group_map[ rule->GetPattern() ] = group.get();
840 group->SetLayerCount( m_parent.m_board->GetCopperLayerCount() );
841 ItemAdded( wxDataViewItem( group->Parent() ), wxDataViewItem( group.get() ) );
842 ++groupId;
843 }
844 }
845
847 {
848 // BeforeReset() drives the canceller, dropping any deferred EnsureVisible before the
849 // clear frees items it may point at.
850 BeforeReset();
851 m_items.clear();
852 AfterReset();
853 }
854
855 void updateItem( const std::optional<LIST_ITEM_ITER>& aRow )
856 {
857 if( aRow )
858 {
859 const std::unique_ptr<LIST_ITEM>& listItem = *aRow.value();
860
861 if( listItem->Parent() )
862 ItemChanged( wxDataViewItem( listItem->Parent() ) );
863
864 ItemChanged( wxDataViewItem( listItem.get() ) );
865 resortIfChanged( listItem.get() );
866 }
867 }
868
870 {
871 for( std::unique_ptr<LIST_ITEM>& i : m_items )
872 ItemChanged( wxDataViewItem( i.get() ) );
873 }
874
876 {
877 if( wxDataViewColumn* column = m_parent.m_netsList->GetSortingColumn() )
878 {
879 bool changed = false;
880
881 for( const LIST_ITEM* i = aItem; i != nullptr; i = i->Parent() )
882 changed |= itemColumnChanged( i, column->GetModelColumn() );
883
884 for( LIST_ITEM* i = aItem; i != nullptr; i = i->Parent() )
885 i->ResetColumnChangedBits();
886
887 if( changed )
888 Resort();
889 }
890 }
891
892 bool itemColumnChanged( const LIST_ITEM* aItem, unsigned int aCol ) const
893 {
894 if( aItem == nullptr || aCol >= m_parent.m_columns.size() )
895 return false;
896
897 if( aCol == COLUMN_PAD_COUNT )
898 return aItem->PadCountChanged();
899
900 else if( aCol == COLUMN_VIA_COUNT )
901 return aItem->ViaCountChanged();
902
903 else if( aCol == COLUMN_VIA_LENGTH )
904 return aItem->ViaLengthChanged();
905
906 else if( aCol == COLUMN_BOARD_LENGTH )
907 return aItem->BoardWireLengthChanged();
908
909 else if( aCol == COLUMN_PAD_DIE_LENGTH )
910 return aItem->PadDieLengthChanged();
911
912 else if( aCol == COLUMN_UNROUTED_LENGTH )
913 return aItem->UnroutedLengthChanged();
914
915 else if( aCol == COLUMN_TOTAL_LENGTH )
916 return aItem->TotalLengthChanged();
917
918 else if( aCol > COLUMN_LAST_STATIC_COL )
919 return aItem->BoardWireLengthChanged();
920
921
922 return false;
923 }
924
925 void SetIsTimeDomain( const bool aIsTimeDomain ) { m_show_time_domain_details = aIsTimeDomain; }
926
927 // implementation of wxDataViewModel interface
928 // these are used to query the data model by the GUI view implementation.
929 // these are not supposed to be used to modify the data model. for that
930 // use the public functions above.
931
932protected:
933 unsigned int GetColumnCount() const override { return columnCount(); }
934
935 void GetValue( wxVariant& aOutValue, const wxDataViewItem& aItem,
936 unsigned int aCol ) const override
937 {
938 if( LIST_ITEM* i = static_cast<LIST_ITEM*>( aItem.GetID() ) )
939 {
940 if( aCol == COLUMN_NAME )
941 {
942 if( i->GetIsGroup() )
943 {
944 switch( i->GetGroupType() )
945 {
947 aOutValue = _( "Netclass" ) + ": " + i->GetGroupName();
948 break;
950 aOutValue = _( "Custom" ) + ": " + i->GetGroupName();
951 break;
952 default:
953 aOutValue = i->GetGroupName();
954 break;
955 }
956 }
957 else
958 {
959 aOutValue = i->GetNetName();
960 }
961 }
962 else if( aCol == COLUMN_NET_CHAIN )
963 {
964 wxString chainName = i->GetNetChainName();
965
966 // If the chain is assigned to a class, append it for visibility:
967 // "DDR_DQ0 [DDR_DATA]"
968 if( !chainName.IsEmpty() && m_parent.m_board )
969 {
970 if( const std::shared_ptr<NET_SETTINGS>& ns =
971 m_parent.m_board->GetDesignSettings().m_NetSettings )
972 {
973 wxString className = ns->GetNetChainClass( chainName );
974
975 if( !className.IsEmpty() )
976 chainName += wxString::Format( wxT( " [%s]" ), className );
977 }
978 }
979
980 aOutValue = chainName;
981 }
982 else if( aCol == COLUMN_NETCLASS )
983 {
984 aOutValue = i->GetNetclassName();
985 }
986 else if( aCol == COLUMN_PAD_COUNT )
987 {
988 aOutValue = m_parent.formatCount( i->GetPadCount() );
989 }
990 else if( aCol == COLUMN_VIA_COUNT )
991 {
992 aOutValue = m_parent.formatCount( i->GetViaCount() );
993 }
994 else if( aCol == COLUMN_VIA_LENGTH )
995 {
997 aOutValue = m_parent.formatDelay( i->GetViaDelay() );
998 else
999 aOutValue = m_parent.formatLength( i->GetViaLength() );
1000 }
1001 else if( aCol == COLUMN_BOARD_LENGTH )
1002 {
1004 aOutValue = m_parent.formatDelay( i->GetBoardWireDelay() );
1005 else
1006 aOutValue = m_parent.formatLength( i->GetBoardWireLength() );
1007 }
1008 else if( aCol == COLUMN_PAD_DIE_LENGTH )
1009 {
1011 aOutValue = m_parent.formatDelay( i->GetPadDieDelay() );
1012 else
1013 aOutValue = m_parent.formatLength( i->GetPadDieLength() );
1014 }
1015 else if( aCol == COLUMN_UNROUTED_LENGTH )
1016 {
1017 aOutValue = m_parent.formatLength( i->GetUnroutedLength() );
1018 }
1019 else if( aCol == COLUMN_TOTAL_LENGTH )
1020 {
1022 aOutValue = m_parent.formatDelay( i->GetTotalDelay() );
1023 else
1024 aOutValue = m_parent.formatLength( i->GetTotalLength() );
1025 }
1026 else if( aCol == COLUMN_NET_CHAIN_LENGTH )
1027 {
1028 if( i->GetNetChainName().IsEmpty() )
1029 aOutValue = "";
1031 aOutValue = m_parent.formatDelay( i->GetNetChainDelay() );
1032 else
1033 aOutValue = m_parent.formatLength( i->GetNetChainLength() );
1034 }
1035 else if( aCol > COLUMN_LAST_STATIC_COL && aCol <= m_parent.m_columns.size() )
1036 {
1038 aOutValue = m_parent.formatDelay( i->GetLayerWireDelay( m_parent.m_columns[aCol].layer ) );
1039 else
1040 aOutValue = m_parent.formatLength( i->GetLayerWireLength( m_parent.m_columns[aCol].layer ) );
1041 }
1042 else
1043 {
1044 aOutValue = "";
1045 }
1046 }
1047 }
1048
1049 bool GetAttr( const wxDataViewItem& aItem, unsigned int aCol, wxDataViewItemAttr& aAttr ) const override
1050 {
1051 if( !aItem.IsOk() || aCol >= m_parent.m_columns.size() )
1052 return false;
1053
1054 const LIST_ITEM& item = *static_cast<const LIST_ITEM*>( aItem.GetID() );
1055
1056 if( const auto zeroValued = isZeroValued( item, aCol ); zeroValued.has_value() )
1057 {
1058 if( zeroValued.value() )
1059 {
1060 aAttr.SetColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
1061 return true;
1062 }
1063 }
1064
1065 return false;
1066 }
1067
1068 static int compareUInt( int64_t aValue1, int64_t aValue2, bool aAsc )
1069 {
1070 if( aAsc )
1071 return aValue1 < aValue2 ? -1 : 1;
1072 else
1073 return aValue2 < aValue1 ? -1 : 1;
1074 }
1075
1076 int Compare( const wxDataViewItem& aItem1, const wxDataViewItem& aItem2, unsigned int aCol,
1077 bool aAsc ) const override
1078 {
1079 const auto compareNetNames =
1080 [&]( const LIST_ITEM& i1, const LIST_ITEM& i2)
1081 {
1082 const wxString& s1 = i1.GetNetName();
1083 const wxString& s2 = i2.GetNetName();
1084
1085 return aAsc ? ValueStringCompare( s1, s2 ) : ValueStringCompare( s2, s1 );
1086 };
1087
1088 const LIST_ITEM& i1 = *static_cast<const LIST_ITEM*>( aItem1.GetID() );
1089 const LIST_ITEM& i2 = *static_cast<const LIST_ITEM*>( aItem2.GetID() );
1090
1091 if( i1.GetIsGroup() && !i2.GetIsGroup() )
1092 return -1;
1093
1094 if( i2.GetIsGroup() && !i1.GetIsGroup() )
1095 return 1;
1096
1097 if( aCol == COLUMN_NAME )
1098 {
1099 int res = compareNetNames( i1, i2 );
1100
1101 if( res != 0 )
1102 return res;
1103 }
1104 else if( aCol == COLUMN_NETCLASS )
1105 {
1106 const wxString& s1 = i1.GetNetclassName();
1107 const wxString& s2 = i2.GetNetclassName();
1108
1109 int res = aAsc ? ValueStringCompare( s1, s2 ) : ValueStringCompare( s2, s1 );
1110
1111 if( res != 0 )
1112 return res;
1113 }
1114 else if( aCol == COLUMN_NET_CHAIN )
1115 {
1116 const wxString& s1 = i1.GetNetChainName();
1117 const wxString& s2 = i2.GetNetChainName();
1118
1119 int res = aAsc ? ValueStringCompare( s1, s2 ) : ValueStringCompare( s2, s1 );
1120
1121 if( res != 0 )
1122 return res;
1123 }
1124 else if( aCol == COLUMN_PAD_COUNT && i1.GetPadCount() != i2.GetPadCount() )
1125 {
1126 return compareUInt( i1.GetPadCount(), i2.GetPadCount(), aAsc );
1127 }
1128 else if( aCol == COLUMN_VIA_COUNT && i1.GetViaCount() != i2.GetViaCount() )
1129 {
1130 return compareUInt( i1.GetViaCount(), i2.GetViaCount(), aAsc );
1131 }
1132 else if( aCol == COLUMN_VIA_LENGTH )
1133 {
1135 return compareUInt( i1.GetViaDelay(), i2.GetViaDelay(), aAsc );
1136
1138 return compareUInt( i1.GetViaLength(), i2.GetViaLength(), aAsc );
1139 }
1140 else if( aCol == COLUMN_BOARD_LENGTH )
1141 {
1143 return compareUInt( i1.GetBoardWireDelay(), i2.GetBoardWireDelay(), aAsc );
1144
1146 return compareUInt( i1.GetBoardWireLength(), i2.GetBoardWireLength(), aAsc );
1147 }
1148 else if( aCol == COLUMN_PAD_DIE_LENGTH )
1149 {
1151 return compareUInt( i1.GetPadDieDelay(), i2.GetPadDieDelay(), aAsc );
1152
1154 return compareUInt( i1.GetPadDieLength(), i2.GetPadDieLength(), aAsc );
1155 }
1156 else if( aCol == COLUMN_UNROUTED_LENGTH && i1.GetUnroutedLength() != i2.GetUnroutedLength() )
1157 {
1158 return compareUInt( i1.GetUnroutedLength(), i2.GetUnroutedLength(), aAsc );
1159 }
1160 else if( aCol == COLUMN_TOTAL_LENGTH )
1161 {
1163 return compareUInt( i1.GetTotalDelay(), i2.GetTotalDelay(), aAsc );
1164
1166 return compareUInt( i1.GetTotalLength(), i2.GetTotalLength(), aAsc );
1167 }
1168 else if( aCol == COLUMN_NET_CHAIN_LENGTH )
1169 {
1171 return compareUInt( i1.GetNetChainDelay(), i2.GetNetChainDelay(), aAsc );
1172
1174 return compareUInt( i1.GetNetChainLength(), i2.GetNetChainLength(), aAsc );
1175 }
1176 else if( aCol > COLUMN_LAST_STATIC_COL && aCol < m_parent.m_columns.size() )
1177 {
1179 && i1.GetLayerWireDelay( m_parent.m_columns[aCol].layer )
1180 != i2.GetLayerWireDelay( m_parent.m_columns[aCol].layer ) )
1181 {
1182 return compareUInt( i1.GetLayerWireDelay( m_parent.m_columns[aCol].layer ),
1183 i2.GetLayerWireDelay( m_parent.m_columns[aCol].layer ), aAsc );
1184 }
1185
1187 && i1.GetLayerWireLength( m_parent.m_columns[aCol].layer )
1188 != i2.GetLayerWireLength( m_parent.m_columns[aCol].layer ) )
1189 {
1190 return compareUInt( i1.GetLayerWireLength( m_parent.m_columns[aCol].layer ),
1191 i2.GetLayerWireLength( m_parent.m_columns[aCol].layer ), aAsc );
1192 }
1193 }
1194
1195 // When the item values compare equal, fall back to net name order so the sort is stable
1196 // across rebuilds, and also in a sensible order for the user to find entries when sorting
1197 // on other columns.
1198 {
1199 int res = compareNetNames( i1, i2 );
1200
1201 if( res != 0 )
1202 return res;
1203 }
1204
1205 // If the entries are STILL equal (how?), fall back to the pointer value, but this won't be
1206 // stable across rebuilds as it is based on a heap allocation.
1207 wxUIntPtr id1 = wxPtrToUInt( aItem1.GetID() );
1208 wxUIntPtr id2 = wxPtrToUInt( aItem2.GetID() );
1209
1210 return aAsc ? id1 - id2 : id2 - id1;
1211 }
1212
1213 bool SetValue( const wxVariant& aInValue, const wxDataViewItem& aItem,
1214 unsigned int aCol ) override
1215 {
1216 return false;
1217 }
1218
1219 wxDataViewItem GetParent( const wxDataViewItem& aItem ) const override
1220 {
1221 if( !aItem.IsOk() )
1222 return wxDataViewItem();
1223
1224 return wxDataViewItem( static_cast<const LIST_ITEM*>( aItem.GetID() )->Parent() );
1225 }
1226
1227 bool IsContainer( const wxDataViewItem& aItem ) const override
1228 {
1229 if( !aItem.IsOk() )
1230 return true;
1231
1232 return static_cast<const LIST_ITEM*>( aItem.GetID() )->GetIsGroup();
1233 }
1234
1235 bool HasContainerColumns( const wxDataViewItem& aItem ) const override
1236 {
1237 return IsContainer( aItem );
1238 }
1239
1240 unsigned int GetChildren( const wxDataViewItem& aParent,
1241 wxDataViewItemArray& aChildren ) const override
1242 {
1243 const LIST_ITEM* p = static_cast<const LIST_ITEM*>( aParent.GetID() );
1244
1245 if( !aParent.IsOk() )
1246 {
1247 aChildren.Alloc( m_items.size() );
1248
1249 for( const std::unique_ptr<LIST_ITEM>& i : m_items )
1250 {
1251 if( i->Parent() == nullptr )
1252 aChildren.Add( wxDataViewItem( &*i ) );
1253 }
1254
1255 return aChildren.GetCount();
1256 }
1257 else if( p->GetIsGroup() )
1258 {
1259 const int count = p->ChildrenCount();
1260
1261 if( count == 0 )
1262 return 0;
1263
1264 aChildren.Alloc( count );
1265
1266 for( auto i = p->ChildrenBegin(), end = p->ChildrenEnd(); i != end; ++i )
1267 aChildren.Add( wxDataViewItem( *i ) );
1268
1269 return aChildren.GetCount();
1270 }
1271
1272 return 0;
1273 }
1274
1275 wxString GetColumnType( unsigned int /* aCol */ ) const override { return wxS( "string" ); }
1276
1277private:
1279
1280 // primary container, sorted by netcode number.
1281 // groups have netcode < 0, so they always come first, in the order
1282 // of the filter strings as input by the user
1283 std::vector<std::unique_ptr<LIST_ITEM>> m_items;
1284
1286 std::map<wxString, LIST_ITEM*> m_custom_group_map;
1287
1289
1296 std::optional<bool> isZeroValued( const LIST_ITEM& aItem, unsigned int aCol ) const
1297 {
1298 switch( aCol )
1299 {
1300 case COLUMN_PAD_COUNT:
1301 return aItem.GetPadCount() == 0;
1302 case COLUMN_VIA_COUNT:
1303 return aItem.GetViaCount() == 0;
1304 default:
1305 break;
1306 }
1307
1308 // Always-length columns
1309 switch( aCol )
1310 {
1312 return aItem.GetUnroutedLength() == 0;
1313 default:
1314 break;
1315 }
1316
1318 {
1319 // In length-mode, these are the lengths
1320 switch( aCol )
1321 {
1323 return aItem.GetTotalLength() == 0;
1325 return aItem.GetNetChainLength() == 0;
1326 case COLUMN_VIA_LENGTH:
1327 return aItem.GetViaLength() == 0;
1329 return aItem.GetBoardWireLength() == 0;
1331 return aItem.GetPadDieLength() == 0;
1333 return aItem.GetUnroutedLength() == 0;
1334 default:
1335 break;
1336 }
1337
1338 // Lengths per-layer
1339 if( aCol > COLUMN_LAST_STATIC_COL && aCol < m_parent.m_columns.size() )
1340 {
1341 const PCB_LAYER_ID layer = m_parent.m_columns[aCol].layer;
1342 return aItem.GetLayerWireLength( layer ) == 0;
1343 }
1344 }
1345 else
1346 {
1347 // In time-domain mode, these are the delays
1348 switch( aCol )
1349 {
1351 return aItem.GetTotalDelay() == 0;
1353 return aItem.GetNetChainDelay() == 0;
1354 case COLUMN_VIA_LENGTH:
1355 return aItem.GetViaDelay() == 0;
1357 return aItem.GetBoardWireDelay() == 0;
1359 return aItem.GetPadDieDelay() == 0;
1360 default:
1361 break;
1362 }
1363
1364 // Delays per-layer
1365 if( aCol > COLUMN_LAST_STATIC_COL && aCol < m_parent.m_columns.size() )
1366 {
1367 const PCB_LAYER_ID layer = m_parent.m_columns[aCol].layer;
1368 return aItem.GetLayerWireDelay( layer ) == 0;
1369 }
1370 }
1371
1372 // Not a zero-able field
1373 return std::nullopt;
1374 }
1375};
1376
1377#endif
const wxString GetHumanReadableName() const
Gets the consolidated name of this netclass (which may be an aggregate).
Definition netclass.cpp:334
Handle the data for a net.
Definition netinfo.h:50
const wxString & GetNetChain() const
Definition netinfo.h:122
const wxString & GetNetname() const
Definition netinfo.h:110
NETCLASS * GetNetClass()
Definition netinfo.h:101
int GetNetCode() const
Definition netinfo.h:104
Data model for display in the Net Inspector panel.
std::unique_ptr< LIST_ITEM > deleteItem(const std::optional< LIST_ITEM_ITER > &aRow)
bool IsContainer(const wxDataViewItem &aItem) const override
bool itemColumnChanged(const LIST_ITEM *aItem, unsigned int aCol) const
static int compareUInt(int64_t aValue1, int64_t aValue2, bool aAsc)
bool GetAttr(const wxDataViewItem &aItem, unsigned int aCol, wxDataViewItemAttr &aAttr) const override
void addCustomGroups()
Adds all custom group-by entries to the items table.
wxString GetColumnType(unsigned int) const override
wxDataViewItem GetParent(const wxDataViewItem &aItem) const override
std::optional< bool > isZeroValued(const LIST_ITEM &aItem, unsigned int aCol) const
bool HasContainerColumns(const wxDataViewItem &aItem) const override
bool SetValue(const wxVariant &aInValue, const wxDataViewItem &aItem, unsigned int aCol) override
void GetValue(wxVariant &aOutValue, const wxDataViewItem &aItem, unsigned int aCol) const override
std::vector< std::pair< wxString, wxDataViewItem > > getGroupDataViewItems()
std::optional< LIST_ITEM_ITER > findItem(NETINFO_ITEM *aNet)
unsigned int GetChildren(const wxDataViewItem &aParent, wxDataViewItemArray &aChildren) const override
std::optional< LIST_ITEM_ITER > findItem(int aNetCode)
std::map< wxString, LIST_ITEM * > m_custom_group_map
Map of custom group names to their representative list item.
wxVariant valueAt(unsigned int aCol, unsigned int aRow) const
void addItems(std::vector< std::unique_ptr< LIST_ITEM > > &aItems)
int Compare(const wxDataViewItem &aItem1, const wxDataViewItem &aItem2, unsigned int aCol, bool aAsc) const override
std::optional< LIST_ITEM_ITER > findGroupItem(int aGroupNumber)
std::vector< std::unique_ptr< LIST_ITEM > > m_items
wxVariant valueForItem(const LIST_ITEM *aItem, unsigned int aCol) const
const LIST_ITEM & itemAt(unsigned int aRow) const
void updateItem(const std::optional< LIST_ITEM_ITER > &aRow)
LIST_ITEM * addGroup(LIST_ITEM_ITER groupsBegin, LIST_ITEM_ITER groupsEnd, wxString groupName, LIST_ITEM::GROUP_TYPE groupType)
std::optional< LIST_ITEM_ITER > addItem(std::unique_ptr< LIST_ITEM > aItem)
Primary data item for entries in the Net Inspector list.
LIST_ITEM(unsigned int aGroupNumber, const wxString &aGroupName, GROUP_TYPE aGroupType)
std::map< PCB_LAYER_ID, int64_t > m_layer_wire_length
void AddLayerWireDelay(const int64_t aValue, PCB_LAYER_ID aLayer)
void SetLayerWireLengths(const std::map< PCB_LAYER_ID, int64_t > &aValue)
void AddLayerWireLength(const int64_t aValue, PCB_LAYER_ID aLayer)
std::map< PCB_LAYER_ID, int64_t > m_layer_wire_delay
LIST_ITEM & operator=(const LIST_ITEM &)=delete
int64_t GetLayerWireDelay(PCB_LAYER_ID aLayer) const
std::map< PCB_LAYER_ID, int64_t > GetLayerWireDelays() const
std::map< PCB_LAYER_ID, int64_t > GetLayerWireLengths() const
int64_t GetLayerWireLength(PCB_LAYER_ID aLayer) const
void SetLayerWireLength(const int64_t aValue, PCB_LAYER_ID aLayer)
void SubLayerWireDelay(const int64_t aValue, PCB_LAYER_ID aLayer)
void SetLayerWireDelays(const std::map< PCB_LAYER_ID, int64_t > &aValue)
void SubLayerWireLength(const int64_t aValue, PCB_LAYER_ID aLayer)
PCB_NET_INSPECTOR_PANEL(wxWindow *parent, PCB_EDIT_FRAME *aFrame)
std::vector< std::unique_ptr< LIST_ITEM > >::iterator LIST_ITEM_ITER
#define _(s)
Abstract pattern-matching tool and implementations.
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
wxString UnescapeString(const wxString &aSource)
int ValueStringCompare(const wxString &strFWord, const wxString &strSWord)
Compare strings like the strcmp function but handle numbers and modifiers within the string text corr...
VECTOR3I res
VECTOR2I end