KiCad PCB EDA Suite
dialog_fields_editor_global.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) 2017 Oliver Walters
5  * Copyright (C) 2017-2018 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 
26 #include <wx/msgdlg.h>
27 #include <wx/grid.h>
28 #include <widgets/wx_grid.h>
29 #include <base_units.h>
30 #include <confirm.h>
31 #include <bitmaps.h>
32 #include <grid_tricks.h>
33 #include <kicad_string.h>
34 #include <build_version.h>
35 #include <general.h>
36 #include <sch_view.h>
37 #include <class_library.h>
38 #include <sch_edit_frame.h>
39 #include <sch_reference_list.h>
40 #include <kiface_i.h>
41 #include <eda_doc.h>
43 
45 
46 
47 enum
48 {
49  MYID_SELECT_FOOTPRINT = 991, // must be within GRID_TRICKS' enum range
51 };
52 
53 
55 {
56 public:
57  FIELDS_EDITOR_GRID_TRICKS( DIALOG_SHIM* aParent, wxGrid* aGrid,
58  wxDataViewListCtrl* aFieldsCtrl ) :
59  GRID_TRICKS( aGrid ),
60  m_dlg( aParent ),
61  m_fieldsCtrl( aFieldsCtrl )
62  {}
63 
64 protected:
65  void showPopupMenu( wxMenu& menu ) override
66  {
67  if( m_grid->GetGridCursorCol() == FOOTPRINT )
68  {
69  menu.Append( MYID_SELECT_FOOTPRINT, _( "Select Footprint..." ), _( "Browse for footprint" ) );
70  menu.AppendSeparator();
71  }
72  else if( m_grid->GetGridCursorCol() == DATASHEET )
73  {
74  menu.Append( MYID_SHOW_DATASHEET, _( "Show Datasheet" ), _( "Show datasheet in browser" ) );
75  menu.AppendSeparator();
76  }
77 
79  }
80 
81  void doPopupSelection( wxCommandEvent& event ) override
82  {
83  if( event.GetId() == MYID_SELECT_FOOTPRINT )
84  {
85  // pick a footprint using the footprint picker.
86  wxString fpid = m_grid->GetCellValue( m_grid->GetGridCursorRow(), FOOTPRINT );
88 
89  if( frame->ShowModal( &fpid, m_dlg ) )
90  m_grid->SetCellValue( m_grid->GetGridCursorRow(), FOOTPRINT, fpid );
91 
92  frame->Destroy();
93  }
94  else if (event.GetId() == MYID_SHOW_DATASHEET )
95  {
96  wxString datasheet_uri = m_grid->GetCellValue( m_grid->GetGridCursorRow(), DATASHEET );
97  datasheet_uri = ResolveUriByEnvVars( datasheet_uri );
98  GetAssociatedDocument( m_dlg, datasheet_uri );
99  }
100  else
101  {
103  }
104 
105  if( event.GetId() >= GRIDTRICKS_FIRST_SHOWHIDE && event.GetId() < GRIDTRICKS_LAST_ID )
106  {
107  // Refresh Show checkboxes from grid columns
108  for( int i = 0; i < m_fieldsCtrl->GetItemCount(); ++i )
109  m_fieldsCtrl->SetToggleValue( m_grid->IsColShown( i ), i, 1 );
110  }
111  }
112 
114  wxDataViewListCtrl* m_fieldsCtrl;
115 };
116 
117 
119 {
125 };
126 
128 {
129  DATA_MODEL_ROW( SCH_REFERENCE aFirstReference, GROUP_TYPE aType )
130  {
131  m_Refs.push_back( aFirstReference );
132  m_Flag = aType;
133  }
134 
136  std::vector<SCH_REFERENCE> m_Refs;
137 };
138 
139 
140 #define FIELD_NAME_COLUMN 0
141 #define SHOW_FIELD_COLUMN 1
142 #define GROUP_BY_COLUMN 2
143 
144 #define QUANTITY_COLUMN ( GetNumberCols() - 1 )
145 
146 #ifdef __WXMAC__
147 #define COLUMN_MARGIN 5
148 #else
149 #define COLUMN_MARGIN 15
150 #endif
151 
152 
153 class FIELDS_EDITOR_GRID_DATA_MODEL : public wxGridTableBase
154 {
155 protected:
156  // The data model is fundamentally m_componentRefs X m_fieldNames.
157 
160  bool m_edited;
161  std::vector<wxString> m_fieldNames;
164 
165  // However, the grid view can vary in two ways:
166  // 1) the componentRefs can be grouped into fewer rows
167  // 2) some columns can be hidden
168  //
169  // We handle (1) here (ie: a table row maps to a group, and the table is rebuilt
170  // when the groupings change), and we let the wxGrid handle (2) (ie: the number
171  // of columns is constant but are hidden/shown by the wxGrid control).
172 
173  std::vector< DATA_MODEL_ROW > m_rows;
174 
175  // Data store
176  // A map of compID : fieldSet, where fieldSet is a map of fieldName : fieldValue
177  std::map< timestamp_t, std::map<wxString, wxString> > m_dataStore;
178 
179 
180 public:
182  m_frame( aFrame ),
183  m_componentRefs( aComponentList ),
184  m_edited( false ),
185  m_sortAscending( false )
186  {
187  m_componentRefs.SplitReferences();
188  }
189 
190 
191  void AddColumn( const wxString& aFieldName )
192  {
193  m_fieldNames.push_back( aFieldName );
194 
195  for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
196  {
197  SCH_COMPONENT* comp = m_componentRefs[ i ].GetComp();
198  timestamp_t compID = comp->GetTimeStamp();
199 
200  m_dataStore[ compID ][ aFieldName ] = comp->GetFieldText( aFieldName, m_frame );
201  }
202  }
203 
204 
205  int GetNumberRows() override { return m_rows.size(); }
206 
207  // Columns are fieldNames + quantity column
208  int GetNumberCols() override { return m_fieldNames.size() + 1; }
209 
210 
211  wxString GetColLabelValue( int aCol ) override
212  {
213  if( aCol == QUANTITY_COLUMN )
214  return _( "Qty" );
215  else
216  return m_fieldNames[ aCol ];
217  }
218 
219 
220  bool IsEmptyCell( int aRow, int aCol ) override
221  {
222  return false; // don't allow adjacent cell overflow, even if we are actually empty
223  }
224 
225 
226  wxString GetValue( int aRow, int aCol ) override
227  {
228  if( aCol == REFERENCE )
229  {
230  // Poor-man's tree controls
231  if( m_rows[ aRow ].m_Flag == GROUP_COLLAPSED )
232  return wxT( "> " ) + GetValue( m_rows[ aRow ], aCol );
233  else if (m_rows[ aRow ].m_Flag == GROUP_EXPANDED )
234  return wxT( "v " ) + GetValue( m_rows[ aRow ], aCol );
235  else if( m_rows[ aRow ].m_Flag == CHILD_ITEM )
236  return wxT( " " ) + GetValue( m_rows[ aRow ], aCol );
237  else
238  return wxT( " " ) + GetValue( m_rows[ aRow ], aCol );
239  }
240  else
241  return GetValue( m_rows[ aRow ], aCol );
242  }
243 
244  std::vector<SCH_REFERENCE> GetRowReferences( int aRow )
245  {
246  wxCHECK( aRow < (int)m_rows.size(), std::vector<SCH_REFERENCE>() );
247  return m_rows[ aRow ].m_Refs;
248  }
249 
250  wxString GetValue( DATA_MODEL_ROW& group, int aCol )
251  {
252  std::vector<SCH_REFERENCE> references;
253  wxString fieldValue;
254 
255  for( const auto& ref : group.m_Refs )
256  {
257  if( aCol == REFERENCE || aCol == QUANTITY_COLUMN )
258  {
259  references.push_back( ref );
260  }
261  else // Other columns are either a single value or ROW_MULTI_ITEMS
262  {
263  timestamp_t compID = ref.GetComp()->GetTimeStamp();
264 
265  if( &ref == &group.m_Refs.front() )
266  fieldValue = m_dataStore[ compID ][ m_fieldNames[ aCol ] ];
267  else if ( fieldValue != m_dataStore[ compID ][ m_fieldNames[ aCol ] ] )
268  return INDETERMINATE;
269  }
270  }
271 
272  if( aCol == REFERENCE || aCol == QUANTITY_COLUMN )
273  {
274  // Remove duplicates (other units of multi-unit parts)
275  std::sort( references.begin(), references.end(),
276  []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
277  {
278  wxString l_ref( l.GetRef() << l.GetRefNumber() );
279  wxString r_ref( r.GetRef() << r.GetRefNumber() );
280  return RefDesStringCompare( l_ref, r_ref ) < 0;
281  } );
282 
283  auto logicalEnd = std::unique( references.begin(), references.end(),
284  []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
285  {
286  // If unannotated then we can't tell what units belong together
287  // so we have to leave them all
288  if( l.GetRefNumber() == wxT( "?" ) )
289  return false;
290 
291  wxString l_ref( l.GetRef() << l.GetRefNumber() );
292  wxString r_ref( r.GetRef() << r.GetRefNumber() );
293  return l_ref == r_ref;
294  } );
295  references.erase( logicalEnd, references.end() );
296  }
297 
298  if( aCol == REFERENCE )
299  {
300  fieldValue = SCH_REFERENCE_LIST::Shorthand( references );
301  }
302  else if( aCol == QUANTITY_COLUMN )
303  {
304  fieldValue = wxString::Format( wxT( "%d" ), ( int )references.size() );
305  }
306 
307  return UnescapeString( fieldValue );
308  }
309 
310 
311  void SetValue( int aRow, int aCol, const wxString &aValue ) override
312  {
313  if( aCol == REFERENCE || aCol == QUANTITY_COLUMN )
314  return; // Can't modify references or quantity
315 
316  wxString value = EscapeString( aValue );
317 
318  DATA_MODEL_ROW& rowGroup = m_rows[ aRow ];
319  wxString fieldName = m_fieldNames[ aCol ];
320 
321  for( const auto& ref : rowGroup.m_Refs )
322  m_dataStore[ ref.GetComp()->GetTimeStamp() ][ fieldName ] = value;
323 
324  m_edited = true;
325  }
326 
327 
328  static bool cmp( const DATA_MODEL_ROW& lhGroup, const DATA_MODEL_ROW& rhGroup,
329  FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol, bool ascending )
330  {
331  // Empty rows always go to the bottom, whether ascending or descending
332  if( lhGroup.m_Refs.size() == 0 )
333  return true;
334  else if( rhGroup.m_Refs.size() == 0 )
335  return false;
336 
337  bool retVal;
338 
339  // Primary sort key is sortCol; secondary is always REFERENCE (column 0)
340 
341  wxString lhs = dataModel->GetValue( (DATA_MODEL_ROW&) lhGroup, sortCol );
342  wxString rhs = dataModel->GetValue( (DATA_MODEL_ROW&) rhGroup, sortCol );
343 
344  if( lhs == rhs || sortCol == REFERENCE )
345  {
346  wxString lhRef = lhGroup.m_Refs[ 0 ].GetRef() + lhGroup.m_Refs[ 0 ].GetRefNumber();
347  wxString rhRef = rhGroup.m_Refs[ 0 ].GetRef() + rhGroup.m_Refs[ 0 ].GetRefNumber();
348  retVal = RefDesStringCompare( lhRef, rhRef ) < 0;
349  }
350  else
351  retVal = ValueStringCompare( lhs, rhs ) < 0;
352 
353  if( ascending )
354  return retVal;
355  else
356  return !retVal;
357  }
358 
359 
360  void Sort( int aColumn, bool ascending )
361  {
362  if( aColumn < 0 )
363  aColumn = 0;
364 
365  m_sortColumn = aColumn;
366  m_sortAscending = ascending;
367 
368  CollapseForSort();
369 
370  std::sort( m_rows.begin(), m_rows.end(),
371  [ this ]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
372  {
373  return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
374  } );
375 
376  ExpandAfterSort();
377  }
378 
379 
380  bool unitMatch( const SCH_REFERENCE& lhRef, const SCH_REFERENCE& rhRef )
381  {
382  // If items are unannotated then we can't tell if they're units of the same
383  // component or not
384  if( lhRef.GetRefNumber() == wxT( "?" ) )
385  return false;
386 
387  return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() );
388  }
389 
390 
391  bool groupMatch( const SCH_REFERENCE& lhRef, const SCH_REFERENCE& rhRef,
392  wxDataViewListCtrl* fieldsCtrl )
393  {
394  bool matchFound = false;
395 
396  // First check the reference column. This can be done directly out of the
397  // SCH_REFERENCEs as the references can't be edited in the grid.
398  if( fieldsCtrl->GetToggleValue( REFERENCE, GROUP_BY_COLUMN ) )
399  {
400  // if we're grouping by reference, then only the prefix must match
401  if( lhRef.GetRef() != rhRef.GetRef() )
402  return false;
403 
404  matchFound = true;
405  }
406 
407  timestamp_t lhRefID = lhRef.GetComp()->GetTimeStamp();
408  timestamp_t rhRefID = rhRef.GetComp()->GetTimeStamp();
409 
410  // Now check all the other columns. This must be done out of the dataStore
411  // for the refresh button to work after editing.
412  for( int i = REFERENCE + 1; i < fieldsCtrl->GetItemCount(); ++i )
413  {
414  if( !fieldsCtrl->GetToggleValue( i, GROUP_BY_COLUMN ) )
415  continue;
416 
417  wxString fieldName = fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN );
418 
419  if( m_dataStore[ lhRefID ][ fieldName ] != m_dataStore[ rhRefID ][ fieldName ] )
420  return false;
421 
422  matchFound = true;
423  }
424 
425  return matchFound;
426  }
427 
428 
429  void RebuildRows( wxCheckBox* groupComponentsBox, wxDataViewListCtrl* fieldsCtrl )
430  {
431  if ( GetView() )
432  {
433  // Commit any pending in-place edits before the row gets moved out from under
434  // the editor.
435  static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
436 
437  wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
438  GetView()->ProcessTableMessage( msg );
439  }
440 
441  m_rows.clear();
442 
443  for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
444  {
445  SCH_REFERENCE ref = m_componentRefs[ i ];
446  bool matchFound = false;
447 
448  // See if we already have a row which this component fits into
449  for( auto& row : m_rows )
450  {
451  // all group members must have identical refs so just use the first one
452  SCH_REFERENCE rowRef = row.m_Refs[ 0 ];
453 
454  if( unitMatch( ref, rowRef ) )
455  {
456  matchFound = true;
457  row.m_Refs.push_back( ref );
458  break;
459  }
460  else if (groupComponentsBox->GetValue() && groupMatch( ref, rowRef, fieldsCtrl ) )
461  {
462  matchFound = true;
463  row.m_Refs.push_back( ref );
464  row.m_Flag = GROUP_COLLAPSED;
465  break;
466  }
467  }
468 
469  if( !matchFound )
470  m_rows.push_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
471  }
472 
473  if ( GetView() )
474  {
475  wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
476  GetView()->ProcessTableMessage( msg );
477  }
478  }
479 
480 
481  void ExpandRow( int aRow )
482  {
483  std::vector<DATA_MODEL_ROW> children;
484 
485  for( auto& ref : m_rows[ aRow ].m_Refs )
486  {
487  bool matchFound = false;
488 
489  // See if we already have a child group which this component fits into
490  for( auto& child : children )
491  {
492  // group members are by definition all matching, so just check
493  // against the first member
494  if( unitMatch( ref, child.m_Refs[ 0 ] ) )
495  {
496  matchFound = true;
497  child.m_Refs.push_back( ref );
498  break;
499  }
500  }
501 
502  if( !matchFound )
503  children.push_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) );
504  }
505 
506  if( children.size() < 2 )
507  return;
508 
509  std::sort( children.begin(), children.end(),
510  [ this ] ( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
511  {
512  return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
513  } );
514 
515  m_rows[ aRow ].m_Flag = GROUP_EXPANDED;
516  m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
517 
518  wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() );
519  GetView()->ProcessTableMessage( msg );
520  }
521 
522 
523  void CollapseRow( int aRow )
524  {
525  auto firstChild = m_rows.begin() + aRow + 1;
526  auto afterLastChild = firstChild;
527  int deleted = 0;
528 
529  while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
530  {
531  deleted++;
532  afterLastChild++;
533  }
534 
535  m_rows[ aRow ].m_Flag = GROUP_COLLAPSED;
536  m_rows.erase( firstChild, afterLastChild );
537 
538  wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
539  GetView()->ProcessTableMessage( msg );
540  }
541 
542 
543  void ExpandCollapseRow( int aRow )
544  {
545  DATA_MODEL_ROW& group = m_rows[ aRow ];
546 
547  if( group.m_Flag == GROUP_COLLAPSED )
548  ExpandRow( aRow );
549  else if( group.m_Flag == GROUP_EXPANDED )
550  CollapseRow( aRow );
551  }
552 
553 
555  {
556  for( size_t i = 0; i < m_rows.size(); ++i )
557  {
558  if( m_rows[ i ].m_Flag == GROUP_EXPANDED )
559  {
560  CollapseRow( i );
561  m_rows[ i ].m_Flag = GROUP_COLLAPSED_DURING_SORT;
562  }
563  }
564  }
565 
566 
568  {
569  for( size_t i = 0; i < m_rows.size(); ++i )
570  {
571  if( m_rows[ i ].m_Flag == GROUP_COLLAPSED_DURING_SORT )
572  ExpandRow( i );
573  }
574  }
575 
576 
577  void ApplyData()
578  {
579  for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
580  {
581  SCH_COMPONENT* comp = m_componentRefs[ i ].GetComp();
582 
583  m_frame->SetCurrentSheet( m_componentRefs[ i ].GetSheetPath() );
584  m_frame->SaveCopyInUndoList( comp, UR_CHANGED, true );
585 
586  std::map<wxString, wxString>& fieldStore = m_dataStore[ comp->GetTimeStamp() ];
587 
588  for( std::pair<wxString, wxString> srcData : fieldStore )
589  {
590  wxString srcName = srcData.first;
591  wxString srcValue = srcData.second;
592  SCH_FIELD* destField = comp->FindField( srcName );
593 
594  if( !destField && !srcValue.IsEmpty() )
595  destField = comp->AddField( SCH_FIELD( wxPoint( 0, 0 ), -1, comp, srcName ) );
596 
597  if( destField && !srcValue.IsEmpty() )
598  destField->SetText( srcValue );
599  else
600  comp->RemoveField( srcName );
601  }
602  }
603 
604  m_edited = false;
605  }
606 
607 
608  int GetDataWidth( int aCol )
609  {
610  int width = 0;
611 
612  if( aCol == REFERENCE )
613  {
614  for( int row = 0; row < GetNumberRows(); ++row )
615  {
616  width = std::max( width, GetTextSize( GetValue( row, aCol ), GetView() ).x );
617  }
618  }
619  else
620  {
621  wxString column_label = GetColLabelValue( aCol ); // component fieldName or Qty string
622 
623  for( unsigned compRef = 0; compRef < m_componentRefs.GetCount(); ++ compRef )
624  {
625  timestamp_t compId = m_componentRefs[ compRef ].GetComp()->GetTimeStamp();
626  wxString text = m_dataStore[ compId ][ column_label ];
627  width = std::max( width, GetTextSize( text, GetView() ).x );
628  }
629  }
630 
631  return width;
632  }
633 
634 
635  bool IsEdited()
636  {
637  return m_edited;
638  }
639 };
640 
641 
644  m_config( Kiface().KifaceSettings() ),
645  m_parent( parent )
646 {
647  wxSize defaultDlgSize = ConvertDialogToPixels( wxSize( 600, 300 ) );
648 
649  // Get all components from the list of schematic sheets
650  SCH_SHEET_LIST sheets( g_RootSheet );
651  sheets.GetComponents( m_componentRefs, false );
652 
653  m_bRefresh->SetBitmap( KiBitmap( refresh_xpm ) );
654 
655  m_fieldsCtrl->AppendTextColumn( _( "Field" ), wxDATAVIEW_CELL_INERT, 0, wxALIGN_LEFT, 0 );
656  m_fieldsCtrl->AppendToggleColumn( _( "Show" ), wxDATAVIEW_CELL_ACTIVATABLE, 0, wxALIGN_CENTER, 0 );
657  m_fieldsCtrl->AppendToggleColumn( _( "Group By" ), wxDATAVIEW_CELL_ACTIVATABLE, 0, wxALIGN_CENTER, 0 );
658 
659  // SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails here on GTK, so we calculate the title sizes and
660  // set the column widths ourselves.
661  auto column = m_fieldsCtrl->GetColumn( SHOW_FIELD_COLUMN );
662  m_showColWidth = GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
663  column->SetWidth( m_showColWidth );
664 
665  column = m_fieldsCtrl->GetColumn( GROUP_BY_COLUMN );
666  m_groupByColWidth = GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
667  column->SetWidth( m_groupByColWidth );
668 
669  // The fact that we're a list should keep the control from reserving space for the
670  // expander buttons... but it doesn't. Fix by forcing the indent to 0.
671  m_fieldsCtrl->SetIndent( 0 );
672 
674 
675  LoadFieldNames(); // loads rows into m_fieldsCtrl and columns into m_dataModel
676 
677  // Now that the fields are loaded we can set the initial location of the splitter
678  // based on the list width. Again, SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails us on GTK.
679  int nameColWidth = 0;
680 
681  for( int row = 0; row < m_fieldsCtrl->GetItemCount(); ++row )
682  {
683  const wxString& fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
684  nameColWidth = std::max( nameColWidth, GetTextSize( fieldName, m_fieldsCtrl ).x );
685  }
686 
687  m_fieldsCtrl->GetColumn( FIELD_NAME_COLUMN )->SetWidth( nameColWidth );
688  m_splitter1->SetSashPosition( nameColWidth + m_showColWidth + m_groupByColWidth + 40 );
689 
691  m_dataModel->Sort( 0, true );
692 
693  // wxGrid's column moving is buggy with native headers and this is one dialog where you'd
694  // really like to be able to rearrange columns.
695  m_grid->UseNativeColHeader( false );
696  m_grid->SetTable( m_dataModel, true );
697 
698  // sync m_grid's column visiblities to Show checkboxes in m_fieldsCtrl
699  for( int i = 0; i < m_fieldsCtrl->GetItemCount(); ++i )
700  {
701  if( m_fieldsCtrl->GetToggleValue( i, 1 ) )
702  m_grid->ShowCol( i );
703  else
704  m_grid->HideCol( i );
705  }
706 
707  // add Cut, Copy, and Paste to wxGrid
708  m_grid->PushEventHandler( new FIELDS_EDITOR_GRID_TRICKS( this, m_grid, m_fieldsCtrl ) );
709 
710  // give a bit more room for comboboxes
711  m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
712 
713  // set reference column attributes
714  wxGridCellAttr* attr = new wxGridCellAttr;
715  attr->SetReadOnly();
716  m_grid->SetColAttr( REFERENCE, attr );
717 
718  // set footprint column browse button
719  attr = new wxGridCellAttr;
720  attr->SetEditor( new GRID_CELL_FOOTPRINT_ID_EDITOR( this ) );
721  m_grid->SetColAttr( FOOTPRINT, attr );
722 
723  // set datasheet column viewer button
724  attr = new wxGridCellAttr;
725  attr->SetEditor( new GRID_CELL_URL_EDITOR( this ) );
726  m_grid->SetColAttr( DATASHEET, attr );
727 
728  // set quantities column attributes
729  attr = new wxGridCellAttr;
730  attr->SetReadOnly();
731  m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
732  m_grid->SetColFormatNumber( m_dataModel->GetColsCount() - 1 );
733 
734  m_grid->AutoSizeColumns( false );
735  for( int col = 0; col < m_grid->GetNumberCols(); ++ col )
736  {
737  // Columns are hidden by setting their width to 0 so if we resize them they will
738  // become unhidden.
739  if( m_grid->IsColShown( col ) )
740  {
741  int textWidth = m_dataModel->GetDataWidth( col ) + COLUMN_MARGIN;
742  int maxWidth = defaultDlgSize.x / 3;
743 
744  if( col == m_grid->GetNumberCols() - 1 )
745  m_grid->SetColSize( col, std::min( std::max( 50, textWidth ), maxWidth ) );
746  else
747  m_grid->SetColSize( col, std::min( std::max( 100, textWidth ), maxWidth ) );
748  }
749  }
750 
751  m_grid->SetGridCursor( 0, 1 );
753 
754  m_sdbSizer1OK->SetDefault();
755 
757  SetSize( defaultDlgSize );
758  Center();
759 
760  // Connect Events
761  m_grid->Connect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort ), NULL, this );
762 }
763 
764 
766 {
767  // Disconnect Events
768  m_grid->Disconnect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort ), NULL, this );
769 
770  // Delete the GRID_TRICKS.
771  m_grid->PopEventHandler( true );
772 
773  // we gave ownership of m_dataModel to the wxGrid...
774 
775  // Clear highligted symbols, if any
776  m_parent->GetCanvas()->GetView()->HighlightItem( nullptr, nullptr );
777  m_parent->GetCanvas()->Refresh();
778 }
779 
780 
782 {
783  if( !m_grid->CommitPendingChanges() )
784  return false;
785 
786  if( !wxDialog::TransferDataFromWindow() )
787  return false;
788 
789  SCH_SHEET_PATH currentSheet = m_parent->GetCurrentSheet();
790 
792  m_parent->SyncView();
793  m_parent->OnModify();
794 
795  // Reset the view to where we left the user
796  m_parent->SetCurrentSheet( currentSheet );
797  m_parent->Refresh();
798 
799  return true;
800 }
801 
802 
803 void DIALOG_FIELDS_EDITOR_GLOBAL::AddField( const wxString& aName,
804  bool defaultShow, bool defaultSortBy )
805 {
806  m_dataModel->AddColumn( aName );
807 
808  wxVector<wxVariant> fieldsCtrlRow;
809 
810  m_config->Read("SymbolFieldEditor/Show/" + aName, &defaultShow);
811  m_config->Read("SymbolFieldEditor/GroupBy/" + aName, &defaultSortBy);
812 
813  fieldsCtrlRow.push_back( wxVariant( aName ) );
814  fieldsCtrlRow.push_back( wxVariant( defaultShow ) );
815  fieldsCtrlRow.push_back( wxVariant( defaultSortBy ) );
816 
817  m_fieldsCtrl->AppendItem( fieldsCtrlRow );
818 }
819 
820 
826 {
827  std::set<wxString> userFieldNames;
828 
829  for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
830  {
831  SCH_COMPONENT* comp = m_componentRefs[ i ].GetComp();
832 
833  for( int j = MANDATORY_FIELDS; j < comp->GetFieldCount(); ++j )
834  userFieldNames.insert( comp->GetField( j )->GetName() );
835  }
836 
837  AddField( _( "Reference" ), true, true );
838  AddField( _( "Value" ), true, true );
839  AddField( _( "Footprint" ), true, true );
840  AddField( _( "Datasheet" ), true, false );
841 
842  for( auto fieldName : userFieldNames )
843  AddField( fieldName, true, false );
844 
845  // Add any templateFieldNames which aren't already present in the userFieldNames
846  for( auto templateFieldName : m_parent->GetTemplateFieldNames() )
847  if( userFieldNames.count( templateFieldName.m_Name ) == 0 )
848  AddField( templateFieldName.m_Name, false, false );
849 }
850 
851 
852 void DIALOG_FIELDS_EDITOR_GLOBAL::OnAddField( wxCommandEvent& event )
853 {
854  // quantities column will become new field column, so it needs to be reset
855  auto attr = new wxGridCellAttr;
856  m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
857  m_grid->SetColFormatCustom( m_dataModel->GetColsCount() - 1, wxGRID_VALUE_STRING );
858 
859  wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
860 
861  if( dlg.ShowModal() != wxID_OK )
862  return;
863 
864  wxString fieldName = dlg.GetValue();
865 
866  if( fieldName.IsEmpty() )
867  {
868  DisplayError( this, _( "Field must have a name." ) );
869  return;
870  }
871 
872  for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
873  {
874  if( fieldName == m_dataModel->GetColLabelValue( i ) )
875  {
876  DisplayError( this, wxString::Format( _( "Field name \"%s\" already in use." ), fieldName ) );
877  return;
878  }
879  }
880 
881  m_config->Write( "SymbolFieldEditor/Show/" + fieldName, true );
882 
883  AddField( fieldName, true, false );
884 
885  wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_INSERTED, m_fieldsCtrl->GetItemCount(), 1 );
886  m_grid->ProcessTableMessage( msg );
887 
888  // set up attributes on the new quantities column
889  attr = new wxGridCellAttr;
890  attr->SetReadOnly();
891  m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
892  m_grid->SetColFormatNumber( m_dataModel->GetColsCount() - 1 );
893  m_grid->SetColSize( m_dataModel->GetColsCount() - 1, 50 );
894 }
895 
896 
898 {
899  wxDataViewItem item = event.GetItem();
900 
901  int row = m_fieldsCtrl->ItemToRow( item );
902  int col = event.GetColumn();
903 
904  switch ( col )
905  {
906  default:
907  break;
908 
909  case SHOW_FIELD_COLUMN:
910  {
911  bool value = m_fieldsCtrl->GetToggleValue( row, col );
912  wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
913  m_config->Write( "SymbolFieldEditor/Show/" + fieldName, value );
914 
915  if( value )
916  m_grid->ShowCol( row );
917  else
918  m_grid->HideCol( row ); // grid's columns map to fieldsCtrl's rows
919  break;
920  }
921 
922  case GROUP_BY_COLUMN:
923  {
924  bool value = m_fieldsCtrl->GetToggleValue( row, col );
925  wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
926  m_config->Write( "SymbolFieldEditor/GroupBy/" + fieldName, value );
928  m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
929  m_grid->ForceRefresh();
930  break;
931  }
932  }
933 }
934 
935 
937 {
939  m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
940  m_grid->ForceRefresh();
941 }
942 
943 
944 void DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort( wxGridEvent& aEvent )
945 {
946  int sortCol = aEvent.GetCol();
947  bool ascending;
948 
949  // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the
950  // event, and if we ask it will give us pre-event info.
951  if( m_grid->IsSortingBy( sortCol ) )
952  // same column; invert ascending
953  ascending = !m_grid->IsSortOrderAscending();
954  else
955  // different column; start with ascending
956  ascending = true;
957 
958  m_dataModel->Sort( sortCol, ascending );
959 }
960 
961 
963 {
964  m_grid->ForceRefresh();
965 }
966 
967 
969 {
971  m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
972  m_grid->ForceRefresh();
973 }
974 
975 
977 {
978  if( event.GetCol() == REFERENCE )
979  {
980  // Clear highligted symbols, if any
981  m_parent->GetCanvas()->GetView()->HighlightItem( nullptr, nullptr );
982 
983  m_dataModel->ExpandCollapseRow( event.GetRow() );
984  std::vector<SCH_REFERENCE> refs = m_dataModel->GetRowReferences( event.GetRow() );
985 
986  // Focus eeschema view on the component selected in the dialog
987  if( refs.size() == 1 )
988  {
989  m_parent->FindComponentAndItem( refs[0].GetRef() + refs[0].GetRefNumber(),
990  true, FIND_COMPONENT_ONLY, wxEmptyString );
991  }
992  }
993  else
994  {
995  event.Skip();
996  }
997 }
998 
999 
1001 {
1002  /* TODO
1003  * - Option to select footprint if FOOTPRINT column selected
1004  */
1005 
1006  event.Skip();
1007 }
1008 
1009 
1011 {
1012  int nameColWidth = event.GetSize().GetX() - m_showColWidth - m_groupByColWidth - 8;
1013 
1014  // GTK loses its head and messes these up when resizing the splitter bar:
1015  m_fieldsCtrl->GetColumn( 1 )->SetWidth( m_showColWidth );
1016  m_fieldsCtrl->GetColumn( 2 )->SetWidth( m_groupByColWidth );
1017 
1018  m_fieldsCtrl->GetColumn( 0 )->SetWidth( nameColWidth );
1019 
1020  event.Skip();
1021 }
1022 
1023 
1025 {
1026  if( TransferDataFromWindow() )
1027  m_parent->SaveProject();
1028 }
1029 
1030 
1031 void DIALOG_FIELDS_EDITOR_GLOBAL::OnCancel( wxCommandEvent& event )
1032 {
1033  Close();
1034 }
1035 
1036 
1037 void DIALOG_FIELDS_EDITOR_GLOBAL::OnClose( wxCloseEvent& event )
1038 {
1039  // This is a cancel, so commit quietly as we're going to throw the results away anyway.
1040  m_grid->CommitPendingChanges( true );
1041 
1042  if( m_dataModel->IsEdited() )
1043  {
1044  if( !HandleUnsavedChanges( this, wxEmptyString,
1045  [&]()->bool { return TransferDataFromWindow(); } ) )
1046  {
1047  event.Veto();
1048  return;
1049  }
1050  }
1051 
1052  event.Skip();
1053 }
void SetCurrentSheet(const SCH_SHEET_PATH &aSheet)
void doPopupSelection(wxCommandEvent &event) override
Class SCH_SHEET_LIST.
Class SCH_FIELD instances are attached to a component and provide a place for the component&#39;s value...
Definition: sch_field.h:56
wxGrid * m_grid
I don&#39;t own the grid, but he owns me.
Definition: grid_tricks.h:58
bool HandleUnsavedChanges(wxWindow *aParent, const wxString &aMessage, const std::function< bool()> &aSaveFunction)
Function HandleUnsavedChanges displays a dialog with Save, Cancel and Discard Changes buttons...
Definition: confirm.cpp:213
Class KIWAY_PLAYER is a wxFrame capable of the OpenProjectFiles function, meaning it can load a porti...
Definition: kiway_player.h:120
SCH_FIELD * FindField(const wxString &aFieldName, bool aIncludeDefaultFields=true)
Search for a SCH_FIELD with aFieldName.
wxString GetName(bool aUseDefaultName=true) const
Function GetName returns the field name.
Definition: sch_field.cpp:435
void OnColumnItemToggled(wxDataViewEvent &event) override
KIWAY & Kiway() const
Function Kiway returns a reference to the KIWAY that this object has an opportunity to participate in...
Definition: kiway_player.h:60
void AddColumn(const wxString &aFieldName)
name of datasheet
void LoadFieldNames()
Constructs the rows of m_fieldsCtrl and the columns of m_dataModel from a union of all field names in...
long timestamp_t
timestamp_t is our type to represent unique IDs for all kinds of elements; historically simply the ti...
Definition: common.h:53
DATA_MODEL_ROW(SCH_REFERENCE aFirstReference, GROUP_TYPE aType)
const wxString ResolveUriByEnvVars(const wxString &aUri)
Replace any environment variables in file-path uris (leaving network-path URIs alone).
Definition: common.cpp:455
std::vector< SCH_REFERENCE > m_Refs
Implementation of conversion functions that require both schematic and board internal units...
This file is part of the common library.
void OnModify()
Must be called after a schematic change in order to set the "modify" flag of the current screen* and ...
This file is part of the common library.
The first 4 are mandatory, and must be instantiated in SCH_COMPONENT and LIB_PART constructors...
Find a component in the schematic.
void FinishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
void OnAddField(wxCommandEvent &event) override
Class GRID_TRICKS is used to add cut, copy, and paste to an otherwise unmodied wxGrid instance...
Definition: grid_tricks.h:51
bool GetAssociatedDocument(wxWindow *aParent, const wxString &aDocName, const wxPathList *aPaths)
Function GetAssociatedDocument open a document (file) with the suitable browser.
Definition: eda_doc.cpp:87
std::map< timestamp_t, std::map< wxString, wxString > > m_dataStore
Schematic editor (Eeschema) main window.
static wxString Shorthand(std::vector< SCH_REFERENCE > aList)
Function Shorthand Returns a shorthand string representing all the references in the list...
Class DIALOG_SHIM may sit in the inheritance tree between wxDialog and any class written by wxFormBui...
Definition: dialog_shim.h:82
void Refresh(bool aEraseBackground=true, const wxRect *aRect=NULL) override
Update the board display after modifying it by a python script (note: it is automatically called by a...
void SetTable(wxGridTableBase *table, bool aTakeOwnership=false)
Hide wxGrid&#39;s SetTable() method with one which doesn&#39;t mess up the grid column widths when setting th...
Definition: wx_grid.cpp:46
bool IsEmptyCell(int aRow, int aCol) override
wxString GetColLabelValue(int aCol) override
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition: dialog_shim.h:115
Field Name Module PCB, i.e. "16DIP300".
void HighlightItem(EDA_ITEM *aItem, LIB_PIN *aPin=nullptr)
Definition: sch_view.cpp:191
Field Reference of part, i.e. "IC21".
KIFACE_I & Kiface()
Global KIFACE_I "get" accessor.
Definition: kicad.cpp:52
SCH_ITEM * FindComponentAndItem(const wxString &aReference, bool aSearchHierarchy, SCH_SEARCH_T aSearchType, const wxString &aSearchText)
Finds a component in the schematic and an item in this component.
Definition: find.cpp:109
Class SCH_REFERENCE_LIST is used to create a flattened list of components because in a complex hierar...
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
DIALOG_FIELDS_EDITOR_GLOBAL(SCH_EDIT_FRAME *parent)
void OnCancel(wxCommandEvent &event) override
wxString EscapeString(const wxString &aSource)
These Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which ar...
Definition: string.cpp:49
void RebuildRows(wxCheckBox *groupComponentsBox, wxDataViewListCtrl *fieldsCtrl)
wxBitmap KiBitmap(BITMAP_DEF aBitmap)
Construct a wxBitmap from a memory record, held in a BITMAP_DEF.
Definition: bitmap.cpp:79
FIELDS_EDITOR_GRID_TRICKS(DIALOG_SHIM *aParent, wxGrid *aGrid, wxDataViewListCtrl *aFieldsCtrl)
FIELDS_EDITOR_GRID_DATA_MODEL(SCH_EDIT_FRAME *aFrame, SCH_REFERENCE_LIST &aComponentList)
void OnSizeFieldList(wxSizeEvent &event) override
SCH_SHEET * g_RootSheet
Definition: eeschema.cpp:56
SCH_FIELD * GetField(int aFieldNdx) const
Returns a field in this symbol.
VTBL_ENTRY KIWAY_PLAYER * Player(FRAME_T aFrameType, bool doCreate=true, wxTopLevelWindow *aParent=NULL)
Function Player returns the KIWAY_PLAYER* given a FRAME_T.
Definition: kiway.cpp:300
int ValueStringCompare(wxString strFWord, wxString strSWord)
Function ValueStringCompare acts just like the strcmp function but handles numbers and modifiers with...
Definition: string.cpp:494
void SyncView()
Mark all items for refresh.
#define INDETERMINATE
SCH_DRAW_PANEL * GetCanvas() const override
const TEMPLATE_FIELDNAMES & GetTemplateFieldNames() const
Return a template field names list for read only access.
SCH_SHEET_PATH & GetCurrentSheet()
void SaveCopyInUndoList(SCH_ITEM *aItemToCopy, UNDO_REDO_T aTypeCommand, bool aAppend=false, const wxPoint &aTransformPoint=wxPoint(0, 0))
Create a copy of the current schematic item, and put it in the undo list.
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add a field to the symbol.
void OnClose(wxCloseEvent &event) override
void OnTableCellClick(wxGridEvent &event) override
void showPopupMenu(wxMenu &menu) override
bool unitMatch(const SCH_REFERENCE &lhRef, const SCH_REFERENCE &rhRef)
Class SCH_SHEET_PATH.
bool groupMatch(const SCH_REFERENCE &lhRef, const SCH_REFERENCE &rhRef, wxDataViewListCtrl *fieldsCtrl)
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition: wx_grid.cpp:156
wxString GetFieldText(const wxString &aFieldName, SCH_EDIT_FRAME *aFrame) const
Search for a field named aFieldName and returns text associated with this field.
void Sort(int aColumn, bool ascending)
KIGFX::SCH_VIEW * GetView() const
#define SHOW_FIELD_COLUMN
YYCODETYPE lhs
wxString GetRef() const
void OnSaveAndContinue(wxCommandEvent &aEvent) override
void OnRegroupComponents(wxCommandEvent &event) override
FIELDS_EDITOR_GRID_DATA_MODEL * m_dataModel
virtual void doPopupSelection(wxCommandEvent &event)
#define QUANTITY_COLUMN
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 GetFieldCount() const
Return the number of fields in this symbol.
virtual void showPopupMenu(wxMenu &menu)
#define max(a, b)
Definition: auxiliary.h:86
void SplitReferences()
Function SplitReferences attempts to split all reference designators into a name (U) and number (1)...
#define FIELD_NAME_COLUMN
static bool cmp(const DATA_MODEL_ROW &lhGroup, const DATA_MODEL_ROW &rhGroup, FIELDS_EDITOR_GRID_DATA_MODEL *dataModel, int sortCol, bool ascending)
wxString UnescapeString(const wxString &aSource)
Definition: string.cpp:93
size_t i
Definition: json11.cpp:597
Class SCH_COMPONENT describes a real schematic component.
Definition: sch_component.h:70
void RemoveField(const wxString &aFieldName)
Removes a user field from the symbol.
#define COLUMN_MARGIN
bool Destroy() override
Our version of Destroy() which is virtual from wxWidgets.
void SetValue(int aRow, int aCol, const wxString &aValue) override
#define GROUP_BY_COLUMN
Definition for part library class.
void GetComponents(SCH_REFERENCE_LIST &aReferences, bool aIncludePowerSymbols=true, bool aForceIncludeOrphanComponents=false)
Function GetComponents adds a SCH_REFERENCE() object to aReferences for each component in the list of...
Class DIALOG_FIELDS_EDITOR_GLOBAL_BASE.
wxString GetValue(int aRow, int aCol) override
unsigned GetCount()
Function GetCount.
void OnTableItemContextMenu(wxGridEvent &event) override
wxString GetRefNumber() const
SCH_COMPONENT * GetComp() const
void DisplayError(wxWindow *parent, const wxString &text, int displaytime)
Function DisplayError displays an error or warning message box with aMessage.
Definition: confirm.cpp:245
void OnTableValueChanged(wxGridEvent &event) override
VTBL_ENTRY bool ShowModal(wxString *aResult=NULL, wxWindow *aResultantFocusWindow=NULL)
Function ShowModal puts up this wxFrame as if it were a modal dialog, with all other instantiated wxF...
std::vector< DATA_MODEL_ROW > m_rows
Class SCH_REFERENCE is used as a helper to define a component&#39;s reference designator in a schematic...
wxString GetValue(DATA_MODEL_ROW &group, int aCol)
timestamp_t GetTimeStamp() const
Definition: base_struct.h:207
void OnGroupComponentsToggled(wxCommandEvent &event) override
#define min(a, b)
Definition: auxiliary.h:85
std::vector< SCH_REFERENCE > GetRowReferences(int aRow)
virtual void SetText(const wxString &aText)
Definition: eda_text.h:154
void AddField(const wxString &aName, bool defaultShow, bool defaultSortBy)
int RefDesStringCompare(wxString strFWord, wxString strSWord)
Function RefDesStringCompare acts just like the strcmp function but treats numbers within the string ...
Definition: string.cpp:542