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 <refdes_utils.h>
35 #include <build_version.h>
36 #include <general.h>
37 #include <sch_view.h>
38 #include <class_library.h>
39 #include <sch_edit_frame.h>
40 #include <sch_reference_list.h>
41 #include <kiface_i.h>
42 #include <eda_doc.h>
44 
46 
47 
48 enum
49 {
50  MYID_SELECT_FOOTPRINT = 991, // must be within GRID_TRICKS' enum range
52 };
53 
54 
56 {
57 public:
59  wxDataViewListCtrl* aFieldsCtrl ) :
60  GRID_TRICKS( aGrid ),
61  m_dlg( aParent ),
62  m_fieldsCtrl( aFieldsCtrl )
63  {}
64 
65 protected:
66  void showPopupMenu( wxMenu& menu ) override
67  {
68  if( m_grid->GetGridCursorCol() == FOOTPRINT )
69  {
70  menu.Append( MYID_SELECT_FOOTPRINT, _( "Select Footprint..." ), _( "Browse for footprint" ) );
71  menu.AppendSeparator();
72  }
73  else if( m_grid->GetGridCursorCol() == DATASHEET )
74  {
75  menu.Append( MYID_SHOW_DATASHEET, _( "Show Datasheet" ), _( "Show datasheet in browser" ) );
76  menu.AppendSeparator();
77  }
78 
80  }
81 
82  void doPopupSelection( wxCommandEvent& event ) override
83  {
84  if( event.GetId() == MYID_SELECT_FOOTPRINT )
85  {
86  // pick a footprint using the footprint picker.
87  wxString fpid = m_grid->GetCellValue( m_grid->GetGridCursorRow(), FOOTPRINT );
89 
90  if( frame->ShowModal( &fpid, m_dlg ) )
91  m_grid->SetCellValue( m_grid->GetGridCursorRow(), FOOTPRINT, fpid );
92 
93  frame->Destroy();
94  }
95  else if (event.GetId() == MYID_SHOW_DATASHEET )
96  {
97  wxString datasheet_uri = m_grid->GetCellValue( m_grid->GetGridCursorRow(), DATASHEET );
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  if( !m_grid->IsColShown( REFERENCE ) )
108  {
109  DisplayError( m_dlg, _( "The Reference column cannot be hidden." ) );
110 
111  m_grid->ShowCol( REFERENCE );
112  }
113 
114  // Refresh Show checkboxes from grid columns
115  for( int i = 0; i < m_fieldsCtrl->GetItemCount(); ++i )
116  m_fieldsCtrl->SetToggleValue( m_grid->IsColShown( i ), i, 1 );
117  }
118  }
119 
121  wxDataViewListCtrl* m_fieldsCtrl;
122 };
123 
124 
126 {
132 };
133 
135 {
136  DATA_MODEL_ROW( SCH_REFERENCE aFirstReference, GROUP_TYPE aType )
137  {
138  m_Refs.push_back( aFirstReference );
139  m_Flag = aType;
140  }
141 
143  std::vector<SCH_REFERENCE> m_Refs;
144 };
145 
146 
147 #define FIELD_NAME_COLUMN 0
148 #define SHOW_FIELD_COLUMN 1
149 #define GROUP_BY_COLUMN 2
150 
151 #define QUANTITY_COLUMN ( GetNumberCols() - 1 )
152 
153 #ifdef __WXMAC__
154 #define COLUMN_MARGIN 5
155 #else
156 #define COLUMN_MARGIN 15
157 #endif
158 
159 
160 class FIELDS_EDITOR_GRID_DATA_MODEL : public wxGridTableBase
161 {
162 protected:
163  // The data model is fundamentally m_componentRefs X m_fieldNames.
164 
167  bool m_edited;
168  std::vector<wxString> m_fieldNames;
171 
172  // However, the grid view can vary in two ways:
173  // 1) the componentRefs can be grouped into fewer rows
174  // 2) some columns can be hidden
175  //
176  // We handle (1) here (ie: a table row maps to a group, and the table is rebuilt
177  // when the groupings change), and we let the wxGrid handle (2) (ie: the number
178  // of columns is constant but are hidden/shown by the wxGrid control).
179 
180  std::vector< DATA_MODEL_ROW > m_rows;
181 
182  // Data store
183  // A map of compID : fieldSet, where fieldSet is a map of fieldName : fieldValue
184  std::map< timestamp_t, std::map<wxString, wxString> > m_dataStore;
185 
186 
187 public:
189  m_frame( aFrame ),
190  m_componentRefs( aComponentList ),
191  m_edited( false ),
192  m_sortColumn( 0 ),
193  m_sortAscending( false )
194  {
196  }
197 
198 
199  void AddColumn( const wxString& aFieldName )
200  {
201  m_fieldNames.push_back( aFieldName );
202 
203  for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
204  {
205  SCH_COMPONENT* comp = m_componentRefs[ i ].GetComp();
206  timestamp_t compID = comp->GetTimeStamp();
207 
208  m_dataStore[ compID ][ aFieldName ] = comp->GetFieldText( aFieldName, m_frame );
209  }
210  }
211 
212 
213  int GetNumberRows() override { return m_rows.size(); }
214 
215  // Columns are fieldNames + quantity column
216  int GetNumberCols() override { return m_fieldNames.size() + 1; }
217 
218 
219  wxString GetColLabelValue( int aCol ) override
220  {
221  if( aCol == QUANTITY_COLUMN )
222  return _( "Qty" );
223  else
224  return m_fieldNames[ aCol ];
225  }
226 
227 
228  bool IsEmptyCell( int aRow, int aCol ) override
229  {
230  return false; // don't allow adjacent cell overflow, even if we are actually empty
231  }
232 
233 
234  wxString GetValue( int aRow, int aCol ) override
235  {
236  if( aCol == REFERENCE )
237  {
238  // Poor-man's tree controls
239  if( m_rows[ aRow ].m_Flag == GROUP_COLLAPSED )
240  return wxT( "> " ) + GetValue( m_rows[ aRow ], aCol );
241  else if (m_rows[ aRow ].m_Flag == GROUP_EXPANDED )
242  return wxT( "v " ) + GetValue( m_rows[ aRow ], aCol );
243  else if( m_rows[ aRow ].m_Flag == CHILD_ITEM )
244  return wxT( " " ) + GetValue( m_rows[ aRow ], aCol );
245  else
246  return wxT( " " ) + GetValue( m_rows[ aRow ], aCol );
247  }
248  else
249  return GetValue( m_rows[ aRow ], aCol );
250  }
251 
252  std::vector<SCH_REFERENCE> GetRowReferences( int aRow )
253  {
254  wxCHECK( aRow < (int)m_rows.size(), std::vector<SCH_REFERENCE>() );
255  return m_rows[ aRow ].m_Refs;
256  }
257 
258  wxString GetValue( DATA_MODEL_ROW& group, int aCol )
259  {
260  std::vector<SCH_REFERENCE> references;
261  wxString fieldValue;
262 
263  for( const auto& ref : group.m_Refs )
264  {
265  if( aCol == REFERENCE || aCol == QUANTITY_COLUMN )
266  {
267  references.push_back( ref );
268  }
269  else // Other columns are either a single value or ROW_MULTI_ITEMS
270  {
271  timestamp_t compID = ref.GetComp()->GetTimeStamp();
272 
273  if( !m_dataStore.count( compID ) ||
274  !m_dataStore[ compID ].count( m_fieldNames[ aCol ] ) )
275  return INDETERMINATE;
276 
277  if( &ref == &group.m_Refs.front() )
278  fieldValue = m_dataStore[ compID ][ m_fieldNames[ aCol ] ];
279  else if ( fieldValue != m_dataStore[ compID ][ m_fieldNames[ aCol ] ] )
280  return INDETERMINATE;
281  }
282  }
283 
284  if( aCol == REFERENCE || aCol == QUANTITY_COLUMN )
285  {
286  // Remove duplicates (other units of multi-unit parts)
287  std::sort( references.begin(), references.end(),
288  []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
289  {
290  wxString l_ref( l.GetRef() << l.GetRefNumber() );
291  wxString r_ref( r.GetRef() << r.GetRefNumber() );
292  return UTIL::RefDesStringCompare( l_ref, r_ref ) < 0;
293  } );
294 
295  auto logicalEnd = std::unique( references.begin(), references.end(),
296  []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
297  {
298  // If unannotated then we can't tell what units belong together
299  // so we have to leave them all
300  if( l.GetRefNumber() == wxT( "?" ) )
301  return false;
302 
303  wxString l_ref( l.GetRef() << l.GetRefNumber() );
304  wxString r_ref( r.GetRef() << r.GetRefNumber() );
305  return l_ref == r_ref;
306  } );
307  references.erase( logicalEnd, references.end() );
308  }
309 
310  if( aCol == REFERENCE )
311  {
312  fieldValue = SCH_REFERENCE_LIST::Shorthand( references );
313  }
314  else if( aCol == QUANTITY_COLUMN )
315  {
316  fieldValue = wxString::Format( wxT( "%d" ), ( int )references.size() );
317  }
318 
319  return fieldValue;
320  }
321 
322 
323  void SetValue( int aRow, int aCol, const wxString &aValue ) override
324  {
325  if( aCol == REFERENCE || aCol == QUANTITY_COLUMN )
326  return; // Can't modify references or quantity
327 
328  wxString value = aValue;
329 
330  DATA_MODEL_ROW& rowGroup = m_rows[ aRow ];
331  wxString fieldName = m_fieldNames[ aCol ];
332 
333  for( const auto& ref : rowGroup.m_Refs )
334  m_dataStore[ ref.GetComp()->GetTimeStamp() ][ fieldName ] = value;
335 
336  m_edited = true;
337  }
338 
339 
340  static bool cmp( const DATA_MODEL_ROW& lhGroup, const DATA_MODEL_ROW& rhGroup,
341  FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol, bool ascending )
342  {
343  // Empty rows always go to the bottom, whether ascending or descending
344  if( lhGroup.m_Refs.size() == 0 )
345  return true;
346  else if( rhGroup.m_Refs.size() == 0 )
347  return false;
348 
349  bool retVal;
350 
351  // Primary sort key is sortCol; secondary is always REFERENCE (column 0)
352 
353  wxString lhs = dataModel->GetValue( (DATA_MODEL_ROW&) lhGroup, sortCol );
354  wxString rhs = dataModel->GetValue( (DATA_MODEL_ROW&) rhGroup, sortCol );
355 
356  if( lhs == rhs || sortCol == REFERENCE )
357  {
358  wxString lhRef = lhGroup.m_Refs[ 0 ].GetRef() + lhGroup.m_Refs[ 0 ].GetRefNumber();
359  wxString rhRef = rhGroup.m_Refs[ 0 ].GetRef() + rhGroup.m_Refs[ 0 ].GetRefNumber();
360  retVal = UTIL::RefDesStringCompare( lhRef, rhRef ) < 0;
361  }
362  else
363  retVal = ValueStringCompare( lhs, rhs ) < 0;
364 
365  if( ascending )
366  return retVal;
367  else
368  return !retVal;
369  }
370 
371 
372  void Sort( int aColumn, bool ascending )
373  {
374  if( aColumn < 0 )
375  aColumn = 0;
376 
377  m_sortColumn = aColumn;
378  m_sortAscending = ascending;
379 
380  CollapseForSort();
381 
382  std::sort( m_rows.begin(), m_rows.end(),
383  [ this ]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
384  {
385  return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
386  } );
387 
388  ExpandAfterSort();
389  }
390 
391 
392  bool unitMatch( const SCH_REFERENCE& lhRef, const SCH_REFERENCE& rhRef )
393  {
394  // If items are unannotated then we can't tell if they're units of the same
395  // component or not
396  if( lhRef.GetRefNumber() == wxT( "?" ) )
397  return false;
398 
399  return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() );
400  }
401 
402 
403  bool groupMatch( const SCH_REFERENCE& lhRef, const SCH_REFERENCE& rhRef,
404  wxDataViewListCtrl* fieldsCtrl )
405  {
406  bool matchFound = false;
407 
408  // First check the reference column. This can be done directly out of the
409  // SCH_REFERENCEs as the references can't be edited in the grid.
410  if( fieldsCtrl->GetToggleValue( REFERENCE, GROUP_BY_COLUMN ) )
411  {
412  // if we're grouping by reference, then only the prefix must match
413  if( lhRef.GetRef() != rhRef.GetRef() )
414  return false;
415 
416  matchFound = true;
417  }
418 
419  timestamp_t lhRefID = lhRef.GetComp()->GetTimeStamp();
420  timestamp_t rhRefID = rhRef.GetComp()->GetTimeStamp();
421 
422  // Now check all the other columns. This must be done out of the dataStore
423  // for the refresh button to work after editing.
424  for( int i = REFERENCE + 1; i < fieldsCtrl->GetItemCount(); ++i )
425  {
426  if( !fieldsCtrl->GetToggleValue( i, GROUP_BY_COLUMN ) )
427  continue;
428 
429  wxString fieldName = fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN );
430 
431  if( m_dataStore[ lhRefID ][ fieldName ] != m_dataStore[ rhRefID ][ fieldName ] )
432  return false;
433 
434  matchFound = true;
435  }
436 
437  return matchFound;
438  }
439 
440 
441  void RebuildRows( wxCheckBox* groupComponentsBox, wxDataViewListCtrl* fieldsCtrl )
442  {
443  if ( GetView() )
444  {
445  // Commit any pending in-place edits before the row gets moved out from under
446  // the editor.
447  static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
448 
449  wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
450  GetView()->ProcessTableMessage( msg );
451  }
452 
453  m_rows.clear();
454 
455  for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
456  {
458  bool matchFound = false;
459 
460  // See if we already have a row which this component fits into
461  for( auto& row : m_rows )
462  {
463  // all group members must have identical refs so just use the first one
464  SCH_REFERENCE rowRef = row.m_Refs[ 0 ];
465 
466  if( unitMatch( ref, rowRef ) )
467  {
468  matchFound = true;
469  row.m_Refs.push_back( ref );
470  break;
471  }
472  else if (groupComponentsBox->GetValue() && groupMatch( ref, rowRef, fieldsCtrl ) )
473  {
474  matchFound = true;
475  row.m_Refs.push_back( ref );
476  row.m_Flag = GROUP_COLLAPSED;
477  break;
478  }
479  }
480 
481  if( !matchFound )
482  m_rows.push_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
483  }
484 
485  if ( GetView() )
486  {
487  wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
488  GetView()->ProcessTableMessage( msg );
489  }
490  }
491 
492 
493  void ExpandRow( int aRow )
494  {
495  std::vector<DATA_MODEL_ROW> children;
496 
497  for( auto& ref : m_rows[ aRow ].m_Refs )
498  {
499  bool matchFound = false;
500 
501  // See if we already have a child group which this component fits into
502  for( auto& child : children )
503  {
504  // group members are by definition all matching, so just check
505  // against the first member
506  if( unitMatch( ref, child.m_Refs[ 0 ] ) )
507  {
508  matchFound = true;
509  child.m_Refs.push_back( ref );
510  break;
511  }
512  }
513 
514  if( !matchFound )
515  children.push_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) );
516  }
517 
518  if( children.size() < 2 )
519  return;
520 
521  std::sort( children.begin(), children.end(),
522  [ this ] ( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
523  {
524  return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
525  } );
526 
527  m_rows[ aRow ].m_Flag = GROUP_EXPANDED;
528  m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
529 
530  wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() );
531  GetView()->ProcessTableMessage( msg );
532  }
533 
534 
535  void CollapseRow( int aRow )
536  {
537  auto firstChild = m_rows.begin() + aRow + 1;
538  auto afterLastChild = firstChild;
539  int deleted = 0;
540 
541  while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
542  {
543  deleted++;
544  afterLastChild++;
545  }
546 
547  m_rows[ aRow ].m_Flag = GROUP_COLLAPSED;
548  m_rows.erase( firstChild, afterLastChild );
549 
550  wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
551  GetView()->ProcessTableMessage( msg );
552  }
553 
554 
555  void ExpandCollapseRow( int aRow )
556  {
557  DATA_MODEL_ROW& group = m_rows[ aRow ];
558 
559  if( group.m_Flag == GROUP_COLLAPSED )
560  ExpandRow( aRow );
561  else if( group.m_Flag == GROUP_EXPANDED )
562  CollapseRow( aRow );
563  }
564 
565 
567  {
568  for( size_t i = 0; i < m_rows.size(); ++i )
569  {
570  if( m_rows[ i ].m_Flag == GROUP_EXPANDED )
571  {
572  CollapseRow( i );
574  }
575  }
576  }
577 
578 
580  {
581  for( size_t i = 0; i < m_rows.size(); ++i )
582  {
583  if( m_rows[ i ].m_Flag == GROUP_COLLAPSED_DURING_SORT )
584  ExpandRow( i );
585  }
586  }
587 
588 
589  void ApplyData()
590  {
591  for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
592  {
593  SCH_COMPONENT& comp = *m_componentRefs[i].GetComp();
594 
595  m_frame->SetCurrentSheet( m_componentRefs[i].GetSheetPath() );
596  m_frame->SaveCopyInUndoList( &comp, UR_CHANGED, true );
597 
598  const std::map<wxString, wxString>& fieldStore = m_dataStore[comp.GetTimeStamp()];
599 
600  for( const std::pair<wxString, wxString> srcData : fieldStore )
601  {
602  const wxString& srcName = srcData.first;
603  const wxString& srcValue = srcData.second;
604  SCH_FIELD* destField = comp.FindField( srcName );
605 
606  if( !destField && !srcValue.IsEmpty() )
607  {
608  const auto compOrigin = comp.GetPosition();
609  destField = comp.AddField( SCH_FIELD( compOrigin, -1, &comp, srcName ) );
610  }
611 
612  if( destField && !srcValue.IsEmpty() )
613  destField->SetText( srcValue );
614  else
615  comp.RemoveField( srcName );
616  }
617  }
618 
619  m_edited = false;
620  }
621 
622 
623  int GetDataWidth( int aCol )
624  {
625  int width = 0;
626 
627  if( aCol == REFERENCE )
628  {
629  for( int row = 0; row < GetNumberRows(); ++row )
630  {
631  width = std::max( width, GetTextSize( GetValue( row, aCol ), GetView() ).x );
632  }
633  }
634  else
635  {
636  wxString column_label = GetColLabelValue( aCol ); // component fieldName or Qty string
637 
638  for( unsigned compRef = 0; compRef < m_componentRefs.GetCount(); ++ compRef )
639  {
640  timestamp_t compId = m_componentRefs[ compRef ].GetComp()->GetTimeStamp();
641  wxString text = m_dataStore[ compId ][ column_label ];
642  width = std::max( width, GetTextSize( text, GetView() ).x );
643  }
644  }
645 
646  return width;
647  }
648 
649 
650  bool IsEdited()
651  {
652  return m_edited;
653  }
654 };
655 
656 
659  m_config( Kiface().KifaceSettings() ),
660  m_parent( parent )
661 {
662  wxSize defaultDlgSize = ConvertDialogToPixels( wxSize( 600, 300 ) );
663 
664  // Get all components from the list of schematic sheets
665  SCH_SHEET_LIST sheets( g_RootSheet );
666  sheets.GetComponents( m_componentRefs, false );
667 
668  m_bRefresh->SetBitmap( KiBitmap( refresh_xpm ) );
669 
670  m_fieldsCtrl->AppendTextColumn( _( "Field" ), wxDATAVIEW_CELL_INERT, 0, wxALIGN_LEFT, 0 );
671  m_fieldsCtrl->AppendToggleColumn( _( "Show" ), wxDATAVIEW_CELL_ACTIVATABLE, 0, wxALIGN_CENTER, 0 );
672  m_fieldsCtrl->AppendToggleColumn( _( "Group By" ), wxDATAVIEW_CELL_ACTIVATABLE, 0, wxALIGN_CENTER, 0 );
673 
674  // SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails here on GTK, so we calculate the title sizes and
675  // set the column widths ourselves.
676  auto column = m_fieldsCtrl->GetColumn( SHOW_FIELD_COLUMN );
677  m_showColWidth = GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
678  column->SetWidth( m_showColWidth );
679 
680  column = m_fieldsCtrl->GetColumn( GROUP_BY_COLUMN );
681  m_groupByColWidth = GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
682  column->SetWidth( m_groupByColWidth );
683 
684  // The fact that we're a list should keep the control from reserving space for the
685  // expander buttons... but it doesn't. Fix by forcing the indent to 0.
686  m_fieldsCtrl->SetIndent( 0 );
687 
689 
690  LoadFieldNames(); // loads rows into m_fieldsCtrl and columns into m_dataModel
691 
692  // Now that the fields are loaded we can set the initial location of the splitter
693  // based on the list width. Again, SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails us on GTK.
694  int nameColWidth = 0;
695 
696  for( int row = 0; row < m_fieldsCtrl->GetItemCount(); ++row )
697  {
698  const wxString& fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
699  nameColWidth = std::max( nameColWidth, GetTextSize( fieldName, m_fieldsCtrl ).x );
700  }
701 
702  m_fieldsCtrl->GetColumn( FIELD_NAME_COLUMN )->SetWidth( nameColWidth );
703  m_splitter1->SetSashPosition( nameColWidth + m_showColWidth + m_groupByColWidth + 40 );
704 
706  m_dataModel->Sort( 0, true );
707 
708  // wxGrid's column moving is buggy with native headers and this is one dialog where you'd
709  // really like to be able to rearrange columns.
710  m_grid->UseNativeColHeader( false );
711  m_grid->SetTable( m_dataModel, true );
712 
713  // sync m_grid's column visiblities to Show checkboxes in m_fieldsCtrl
714  for( int i = 0; i < m_fieldsCtrl->GetItemCount(); ++i )
715  {
716  if( m_fieldsCtrl->GetToggleValue( i, 1 ) )
717  m_grid->ShowCol( i );
718  else
719  m_grid->HideCol( i );
720  }
721 
722  // add Cut, Copy, and Paste to wxGrid
723  m_grid->PushEventHandler( new FIELDS_EDITOR_GRID_TRICKS( this, m_grid, m_fieldsCtrl ) );
724 
725  // give a bit more room for comboboxes
726  m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
727 
728  // set reference column attributes
729  wxGridCellAttr* attr = new wxGridCellAttr;
730  attr->SetReadOnly();
731  m_grid->SetColAttr( REFERENCE, attr );
732 
733  // set footprint column browse button
734  attr = new wxGridCellAttr;
735  attr->SetEditor( new GRID_CELL_FOOTPRINT_ID_EDITOR( this ) );
736  m_grid->SetColAttr( FOOTPRINT, attr );
737 
738  // set datasheet column viewer button
739  attr = new wxGridCellAttr;
740  attr->SetEditor( new GRID_CELL_URL_EDITOR( this ) );
741  m_grid->SetColAttr( DATASHEET, attr );
742 
743  // set quantities column attributes
744  attr = new wxGridCellAttr;
745  attr->SetReadOnly();
746  m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
747  m_grid->SetColFormatNumber( m_dataModel->GetColsCount() - 1 );
748 
749  m_grid->AutoSizeColumns( false );
750  for( int col = 0; col < m_grid->GetNumberCols(); ++ col )
751  {
752  // Columns are hidden by setting their width to 0 so if we resize them they will
753  // become unhidden.
754  if( m_grid->IsColShown( col ) )
755  {
756  int textWidth = m_dataModel->GetDataWidth( col ) + COLUMN_MARGIN;
757  int maxWidth = defaultDlgSize.x / 3;
758 
759  if( col == m_grid->GetNumberCols() - 1 )
760  m_grid->SetColSize( col, std::min( std::max( 50, textWidth ), maxWidth ) );
761  else
762  m_grid->SetColSize( col, std::min( std::max( 100, textWidth ), maxWidth ) );
763  }
764  }
765 
766  m_grid->SetGridCursor( 0, 1 );
768 
769  m_sdbSizer1OK->SetDefault();
770 
772  SetSize( defaultDlgSize );
773  Center();
774 
775  // Connect Events
776  m_grid->Connect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort ), NULL, this );
777 }
778 
779 
781 {
782  // Disconnect Events
783  m_grid->Disconnect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort ), NULL, this );
784 
785  // Delete the GRID_TRICKS.
786  m_grid->PopEventHandler( true );
787 
788  // we gave ownership of m_dataModel to the wxGrid...
789 
790  // Clear highligted symbols, if any
791  m_parent->GetCanvas()->GetView()->HighlightItem( nullptr, nullptr );
792  m_parent->GetCanvas()->Refresh();
793 }
794 
795 
797 {
798  if( !m_grid->CommitPendingChanges() )
799  return false;
800 
801  if( !wxDialog::TransferDataFromWindow() )
802  return false;
803 
804  SCH_SHEET_PATH currentSheet = m_parent->GetCurrentSheet();
805 
807  m_parent->SyncView();
808  m_parent->OnModify();
809 
810  // Reset the view to where we left the user
811  m_parent->SetCurrentSheet( currentSheet );
812  m_parent->Refresh();
813 
814  return true;
815 }
816 
817 
818 void DIALOG_FIELDS_EDITOR_GLOBAL::AddField( const wxString& aName,
819  bool defaultShow, bool defaultSortBy )
820 {
821  m_dataModel->AddColumn( aName );
822 
823  wxVector<wxVariant> fieldsCtrlRow;
824 
825  m_config->Read( "SymbolFieldEditor/Show/" + aName, &defaultShow );
826  m_config->Read( "SymbolFieldEditor/GroupBy/" + aName, &defaultSortBy );
827 
828  fieldsCtrlRow.push_back( wxVariant( aName ) );
829  fieldsCtrlRow.push_back( wxVariant( defaultShow ) );
830  fieldsCtrlRow.push_back( wxVariant( defaultSortBy ) );
831 
832  m_fieldsCtrl->AppendItem( fieldsCtrlRow );
833 }
834 
835 
841 {
842  std::set<wxString> userFieldNames;
843 
844  for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
845  {
846  SCH_COMPONENT* comp = m_componentRefs[ i ].GetComp();
847 
848  for( int j = MANDATORY_FIELDS; j < comp->GetFieldCount(); ++j )
849  userFieldNames.insert( comp->GetField( j )->GetName() );
850  }
851 
852  // Force References to always be shown
853  m_config->Write( "SymbolFieldEditor/Show/Reference", true );
854 
855  AddField( _( "Reference" ), true, true );
856  AddField( _( "Value" ), true, true );
857  AddField( _( "Footprint" ), true, true );
858  AddField( _( "Datasheet" ), true, false );
859 
860  for( auto fieldName : userFieldNames )
861  AddField( fieldName, true, false );
862 
863  // Add any templateFieldNames which aren't already present in the userFieldNames
864  for( auto templateFieldName : m_parent->GetTemplateFieldNames() )
865  if( userFieldNames.count( templateFieldName.m_Name ) == 0 )
866  AddField( templateFieldName.m_Name, false, false );
867 }
868 
869 
870 void DIALOG_FIELDS_EDITOR_GLOBAL::OnAddField( wxCommandEvent& event )
871 {
872  // quantities column will become new field column, so it needs to be reset
873  auto attr = new wxGridCellAttr;
874  m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
875  m_grid->SetColFormatCustom( m_dataModel->GetColsCount() - 1, wxGRID_VALUE_STRING );
876 
877  wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
878 
879  if( dlg.ShowModal() != wxID_OK )
880  return;
881 
882  wxString fieldName = dlg.GetValue();
883 
884  if( fieldName.IsEmpty() )
885  {
886  DisplayError( this, _( "Field must have a name." ) );
887  return;
888  }
889 
890  for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
891  {
892  if( fieldName == m_dataModel->GetColLabelValue( i ) )
893  {
894  DisplayError( this, wxString::Format( _( "Field name \"%s\" already in use." ), fieldName ) );
895  return;
896  }
897  }
898 
899  m_config->Write( "SymbolFieldEditor/Show/" + fieldName, true );
900 
901  AddField( fieldName, true, false );
902 
903  wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_INSERTED, m_fieldsCtrl->GetItemCount(), 1 );
904  m_grid->ProcessTableMessage( msg );
905 
906  // set up attributes on the new quantities column
907  attr = new wxGridCellAttr;
908  attr->SetReadOnly();
909  m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
910  m_grid->SetColFormatNumber( m_dataModel->GetColsCount() - 1 );
911  m_grid->SetColSize( m_dataModel->GetColsCount() - 1, 50 );
912 }
913 
914 
916 {
917  wxDataViewItem item = event.GetItem();
918 
919  int row = m_fieldsCtrl->ItemToRow( item );
920  int col = event.GetColumn();
921 
922  switch ( col )
923  {
924  default:
925  break;
926 
927  case SHOW_FIELD_COLUMN:
928  {
929  bool value = m_fieldsCtrl->GetToggleValue( row, col );
930 
931  if( row == REFERENCE && !value )
932  {
933  DisplayError( this, _( "The Reference column cannot be hidden." ) );
934 
935  value = true;
936  m_fieldsCtrl->SetToggleValue( value, row, col );
937  }
938 
939  wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
940  m_config->Write( "SymbolFieldEditor/Show/" + fieldName, value );
941 
942  if( value )
943  m_grid->ShowCol( row );
944  else
945  m_grid->HideCol( row ); // grid's columns map to fieldsCtrl's rows
946  break;
947  }
948 
949  case GROUP_BY_COLUMN:
950  {
951  bool value = m_fieldsCtrl->GetToggleValue( row, col );
952  wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
953  m_config->Write( "SymbolFieldEditor/GroupBy/" + fieldName, value );
955  m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
956  m_grid->ForceRefresh();
957  break;
958  }
959  }
960 }
961 
962 
964 {
966  m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
967  m_grid->ForceRefresh();
968 }
969 
970 
971 void DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort( wxGridEvent& aEvent )
972 {
973  int sortCol = aEvent.GetCol();
974  bool ascending;
975 
976  // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the
977  // event, and if we ask it will give us pre-event info.
978  if( m_grid->IsSortingBy( sortCol ) )
979  // same column; invert ascending
980  ascending = !m_grid->IsSortOrderAscending();
981  else
982  // different column; start with ascending
983  ascending = true;
984 
985  m_dataModel->Sort( sortCol, ascending );
986 }
987 
988 
990 {
991  m_grid->ForceRefresh();
992 }
993 
994 
996 {
998  m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
999  m_grid->ForceRefresh();
1000 }
1001 
1002 
1004 {
1005  if( event.GetCol() == REFERENCE )
1006  {
1007  m_grid->ClearSelection();
1008  m_grid->SetGridCursor( event.GetRow(), event.GetCol() );
1009 
1010  // Clear highligted symbols, if any
1011  m_parent->GetCanvas()->GetView()->HighlightItem( nullptr, nullptr );
1012  m_parent->GetCanvas()->Refresh();
1013 
1014  m_dataModel->ExpandCollapseRow( event.GetRow() );
1015  std::vector<SCH_REFERENCE> refs = m_dataModel->GetRowReferences( event.GetRow() );
1016 
1017  // Focus eeschema view on the component selected in the dialog
1018  if( refs.size() == 1 )
1019  {
1020  m_parent->FindComponentAndItem( refs[0].GetRef() + refs[0].GetRefNumber(),
1021  true, FIND_COMPONENT_ONLY, wxEmptyString );
1022  }
1023  }
1024  else
1025  {
1026  event.Skip();
1027  }
1028 }
1029 
1030 
1032 {
1033  /* TODO
1034  * - Option to select footprint if FOOTPRINT column selected
1035  */
1036 
1037  event.Skip();
1038 }
1039 
1040 
1042 {
1043  int nameColWidth = event.GetSize().GetX() - m_showColWidth - m_groupByColWidth - 8;
1044 
1045  // GTK loses its head and messes these up when resizing the splitter bar:
1046  m_fieldsCtrl->GetColumn( 1 )->SetWidth( m_showColWidth );
1047  m_fieldsCtrl->GetColumn( 2 )->SetWidth( m_groupByColWidth );
1048 
1049  m_fieldsCtrl->GetColumn( 0 )->SetWidth( nameColWidth );
1050 
1051  event.Skip();
1052 }
1053 
1054 
1056 {
1057  if( TransferDataFromWindow() )
1058  m_parent->SaveProject();
1059 }
1060 
1061 
1062 void DIALOG_FIELDS_EDITOR_GLOBAL::OnCancel( wxCommandEvent& event )
1063 {
1064  Close();
1065 }
1066 
1067 
1068 void DIALOG_FIELDS_EDITOR_GLOBAL::OnClose( wxCloseEvent& event )
1069 {
1070  // This is a cancel, so commit quietly as we're going to throw the results away anyway.
1071  m_grid->CommitPendingChanges( true );
1072 
1073  if( m_dataModel->IsEdited() )
1074  {
1075  if( !HandleUnsavedChanges( this, wxEmptyString,
1076  [&]()->bool { return TransferDataFromWindow(); } ) )
1077  {
1078  event.Veto();
1079  return;
1080  }
1081  }
1082 
1083  event.Skip();
1084 }
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's value,...
Definition: sch_field.h:52
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:212
Class KIWAY_PLAYER is a wxFrame capable of the OpenProjectFiles function, meaning it can load a porti...
Definition: kiway_player.h:127
SCH_FIELD * FindField(const wxString &aFieldName, bool aIncludeDefaultFields=true)
Search for a SCH_FIELD with aFieldName.
KIWAY & Kiway() const
Function Kiway returns a reference to the KIWAY that this object has an opportunity to participate in...
Definition: kiway_player.h:61
void OnColumnItemToggled(wxDataViewEvent &event) override
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...
DATA_MODEL_ROW(SCH_REFERENCE aFirstReference, GROUP_TYPE aType)
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.
This file is part of the common library.
FIELDS_EDITOR_GRID_TRICKS(DIALOG_SHIM *aParent, WX_GRID *aGrid, wxDataViewListCtrl *aFieldsCtrl)
SCH_COMPONENT * GetComp() const
void SetText(const wxString &aText) override
Definition: sch_field.cpp:125
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
Collection of utility functions for component reference designators (refdes)
Class GRID_TRICKS is used to add mouse and command handling (such as cut, copy, and paste) to a WX_GR...
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:83
wxString GetRefNumber() const
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's SetTable() method with one which doesn't mess up the grid column widths when setting th...
Definition: wx_grid.cpp:57
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:116
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:115
DIALOG_FIELDS_EDITOR_GLOBAL(SCH_EDIT_FRAME *parent)
SCH_FIELD * GetField(int aFieldNdx) const
Returns a field in this symbol.
void OnCancel(wxCommandEvent &event) override
WX_GRID * m_grid
I don't own the grid, but he owns me.
Definition: grid_tricks.h:58
int RefDesStringCompare(const wxString &aFirst, const wxString &aSecond)
Acts just like the strcmp function but treats numbers within the string text correctly for sorting.
wxString GetRef() const
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_DATA_MODEL(SCH_EDIT_FRAME *aFrame, SCH_REFERENCE_LIST &aComponentList)
void OnSizeFieldList(wxSizeEvent &event) override
SCH_SHEET * g_RootSheet
Definition: eeschema.cpp:56
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:321
int ValueStringCompare(wxString strFWord, wxString strSWord)
Compare strings like the strcmp function but handle numbers and modifiers within the string text corr...
Definition: string.cpp:529
void SyncView()
Mark all items for refresh.
#define INDETERMINATE
SCH_DRAW_PANEL * GetCanvas() const override
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.
timestamp_t GetTimeStamp() const
Definition: base_struct.h:210
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:167
void Sort(int aColumn, bool ascending)
int GetFieldCount() const
Return the number of fields in this symbol.
KIGFX::SCH_VIEW * GetView() const
#define SHOW_FIELD_COLUMN
YYCODETYPE lhs
void OnSaveAndContinue(wxCommandEvent &aEvent) override
void OnRegroupComponents(wxCommandEvent &event) override
wxString GetFieldText(const wxString &aFieldName, SCH_EDIT_FRAME *aFrame) const
Search for a field named aFieldName and returns text associated with this field.
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
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 GetName(bool aUseDefaultName=true) const
Function GetName returns the field name.
Definition: sch_field.cpp:427
size_t i
Definition: json11.cpp:597
Class SCH_COMPONENT describes a real schematic component.
Definition: sch_component.h:73
wxPoint GetPosition() const override
Function GetPosition.
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
uint32_t timestamp_t
timestamp_t is our type to represent unique IDs for all kinds of elements; historically simply the ti...
Definition: common.h:53
void OnModify() override
Must be called after a schematic change in order to set the "modify" flag of the current screen and u...
const TEMPLATE_FIELDNAMES & GetTemplateFieldNames() const
Return a template field names list for read only access.
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
void DisplayError(wxWindow *parent, const wxString &text, int displaytime)
Function DisplayError displays an error or warning message box with aMessage.
Definition: confirm.cpp:244
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's reference designator in a schematic.
wxString GetValue(DATA_MODEL_ROW &group, int aCol)
void OnGroupComponentsToggled(wxCommandEvent &event) override
#define min(a, b)
Definition: auxiliary.h:85
std::vector< SCH_REFERENCE > GetRowReferences(int aRow)
void AddField(const wxString &aName, bool defaultShow, bool defaultSortBy)