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 UnescapeString( 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 = EscapeString( 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  std::map<wxString, wxString>& fieldStore = m_dataStore[ comp->GetTimeStamp() ];
599 
600  for( std::pair<wxString, wxString> srcData : fieldStore )
601  {
602  wxString srcName = srcData.first;
603  wxString srcValue = srcData.second;
604  SCH_FIELD* destField = comp->FindField( srcName );
605 
606  if( !destField && !srcValue.IsEmpty() )
607  destField = comp->AddField( SCH_FIELD( wxPoint( 0, 0 ), -1, comp, srcName ) );
608 
609  if( destField && !srcValue.IsEmpty() )
610  destField->SetText( srcValue );
611  else
612  comp->RemoveField( srcName );
613  }
614  }
615 
616  m_edited = false;
617  }
618 
619 
620  int GetDataWidth( int aCol )
621  {
622  int width = 0;
623 
624  if( aCol == REFERENCE )
625  {
626  for( int row = 0; row < GetNumberRows(); ++row )
627  {
628  width = std::max( width, GetTextSize( GetValue( row, aCol ), GetView() ).x );
629  }
630  }
631  else
632  {
633  wxString column_label = GetColLabelValue( aCol ); // component fieldName or Qty string
634 
635  for( unsigned compRef = 0; compRef < m_componentRefs.GetCount(); ++ compRef )
636  {
637  timestamp_t compId = m_componentRefs[ compRef ].GetComp()->GetTimeStamp();
638  wxString text = m_dataStore[ compId ][ column_label ];
639  width = std::max( width, GetTextSize( text, GetView() ).x );
640  }
641  }
642 
643  return width;
644  }
645 
646 
647  bool IsEdited()
648  {
649  return m_edited;
650  }
651 };
652 
653 
656  m_config( Kiface().KifaceSettings() ),
657  m_parent( parent )
658 {
659  wxSize defaultDlgSize = ConvertDialogToPixels( wxSize( 600, 300 ) );
660 
661  // Get all components from the list of schematic sheets
662  SCH_SHEET_LIST sheets( g_RootSheet );
663  sheets.GetComponents( m_componentRefs, false );
664 
665  m_bRefresh->SetBitmap( KiBitmap( refresh_xpm ) );
666 
667  m_fieldsCtrl->AppendTextColumn( _( "Field" ), wxDATAVIEW_CELL_INERT, 0, wxALIGN_LEFT, 0 );
668  m_fieldsCtrl->AppendToggleColumn( _( "Show" ), wxDATAVIEW_CELL_ACTIVATABLE, 0, wxALIGN_CENTER, 0 );
669  m_fieldsCtrl->AppendToggleColumn( _( "Group By" ), wxDATAVIEW_CELL_ACTIVATABLE, 0, wxALIGN_CENTER, 0 );
670 
671  // SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails here on GTK, so we calculate the title sizes and
672  // set the column widths ourselves.
673  auto column = m_fieldsCtrl->GetColumn( SHOW_FIELD_COLUMN );
674  m_showColWidth = GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
675  column->SetWidth( m_showColWidth );
676 
677  column = m_fieldsCtrl->GetColumn( GROUP_BY_COLUMN );
678  m_groupByColWidth = GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
679  column->SetWidth( m_groupByColWidth );
680 
681  // The fact that we're a list should keep the control from reserving space for the
682  // expander buttons... but it doesn't. Fix by forcing the indent to 0.
683  m_fieldsCtrl->SetIndent( 0 );
684 
686 
687  LoadFieldNames(); // loads rows into m_fieldsCtrl and columns into m_dataModel
688 
689  // Now that the fields are loaded we can set the initial location of the splitter
690  // based on the list width. Again, SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails us on GTK.
691  int nameColWidth = 0;
692 
693  for( int row = 0; row < m_fieldsCtrl->GetItemCount(); ++row )
694  {
695  const wxString& fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
696  nameColWidth = std::max( nameColWidth, GetTextSize( fieldName, m_fieldsCtrl ).x );
697  }
698 
699  m_fieldsCtrl->GetColumn( FIELD_NAME_COLUMN )->SetWidth( nameColWidth );
700  m_splitter1->SetSashPosition( nameColWidth + m_showColWidth + m_groupByColWidth + 40 );
701 
703  m_dataModel->Sort( 0, true );
704 
705  // wxGrid's column moving is buggy with native headers and this is one dialog where you'd
706  // really like to be able to rearrange columns.
707  m_grid->UseNativeColHeader( false );
708  m_grid->SetTable( m_dataModel, true );
709 
710  // sync m_grid's column visiblities to Show checkboxes in m_fieldsCtrl
711  for( int i = 0; i < m_fieldsCtrl->GetItemCount(); ++i )
712  {
713  if( m_fieldsCtrl->GetToggleValue( i, 1 ) )
714  m_grid->ShowCol( i );
715  else
716  m_grid->HideCol( i );
717  }
718 
719  // add Cut, Copy, and Paste to wxGrid
720  m_grid->PushEventHandler( new FIELDS_EDITOR_GRID_TRICKS( this, m_grid, m_fieldsCtrl ) );
721 
722  // give a bit more room for comboboxes
723  m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
724 
725  // set reference column attributes
726  wxGridCellAttr* attr = new wxGridCellAttr;
727  attr->SetReadOnly();
728  m_grid->SetColAttr( REFERENCE, attr );
729 
730  // set footprint column browse button
731  attr = new wxGridCellAttr;
732  attr->SetEditor( new GRID_CELL_FOOTPRINT_ID_EDITOR( this ) );
733  m_grid->SetColAttr( FOOTPRINT, attr );
734 
735  // set datasheet column viewer button
736  attr = new wxGridCellAttr;
737  attr->SetEditor( new GRID_CELL_URL_EDITOR( this ) );
738  m_grid->SetColAttr( DATASHEET, attr );
739 
740  // set quantities column attributes
741  attr = new wxGridCellAttr;
742  attr->SetReadOnly();
743  m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
744  m_grid->SetColFormatNumber( m_dataModel->GetColsCount() - 1 );
745 
746  m_grid->AutoSizeColumns( false );
747  for( int col = 0; col < m_grid->GetNumberCols(); ++ col )
748  {
749  // Columns are hidden by setting their width to 0 so if we resize them they will
750  // become unhidden.
751  if( m_grid->IsColShown( col ) )
752  {
753  int textWidth = m_dataModel->GetDataWidth( col ) + COLUMN_MARGIN;
754  int maxWidth = defaultDlgSize.x / 3;
755 
756  if( col == m_grid->GetNumberCols() - 1 )
757  m_grid->SetColSize( col, std::min( std::max( 50, textWidth ), maxWidth ) );
758  else
759  m_grid->SetColSize( col, std::min( std::max( 100, textWidth ), maxWidth ) );
760  }
761  }
762 
763  m_grid->SetGridCursor( 0, 1 );
765 
766  m_sdbSizer1OK->SetDefault();
767 
769  SetSize( defaultDlgSize );
770  Center();
771 
772  // Connect Events
773  m_grid->Connect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort ), NULL, this );
774 }
775 
776 
778 {
779  // Disconnect Events
780  m_grid->Disconnect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort ), NULL, this );
781 
782  // Delete the GRID_TRICKS.
783  m_grid->PopEventHandler( true );
784 
785  // we gave ownership of m_dataModel to the wxGrid...
786 
787  // Clear highligted symbols, if any
788  m_parent->GetCanvas()->GetView()->HighlightItem( nullptr, nullptr );
789  m_parent->GetCanvas()->Refresh();
790 }
791 
792 
794 {
795  if( !m_grid->CommitPendingChanges() )
796  return false;
797 
798  if( !wxDialog::TransferDataFromWindow() )
799  return false;
800 
801  SCH_SHEET_PATH currentSheet = m_parent->GetCurrentSheet();
802 
804  m_parent->SyncView();
805  m_parent->OnModify();
806 
807  // Reset the view to where we left the user
808  m_parent->SetCurrentSheet( currentSheet );
809  m_parent->Refresh();
810 
811  return true;
812 }
813 
814 
815 void DIALOG_FIELDS_EDITOR_GLOBAL::AddField( const wxString& aName,
816  bool defaultShow, bool defaultSortBy )
817 {
818  m_dataModel->AddColumn( aName );
819 
820  wxVector<wxVariant> fieldsCtrlRow;
821 
822  m_config->Read( "SymbolFieldEditor/Show/" + aName, &defaultShow );
823  m_config->Read( "SymbolFieldEditor/GroupBy/" + aName, &defaultSortBy );
824 
825  fieldsCtrlRow.push_back( wxVariant( aName ) );
826  fieldsCtrlRow.push_back( wxVariant( defaultShow ) );
827  fieldsCtrlRow.push_back( wxVariant( defaultSortBy ) );
828 
829  m_fieldsCtrl->AppendItem( fieldsCtrlRow );
830 }
831 
832 
838 {
839  std::set<wxString> userFieldNames;
840 
841  for( unsigned i = 0; i < m_componentRefs.GetCount(); ++i )
842  {
843  SCH_COMPONENT* comp = m_componentRefs[ i ].GetComp();
844 
845  for( int j = MANDATORY_FIELDS; j < comp->GetFieldCount(); ++j )
846  userFieldNames.insert( comp->GetField( j )->GetName() );
847  }
848 
849  // Force References to always be shown
850  m_config->Write( "SymbolFieldEditor/Show/Reference", true );
851 
852  AddField( _( "Reference" ), true, true );
853  AddField( _( "Value" ), true, true );
854  AddField( _( "Footprint" ), true, true );
855  AddField( _( "Datasheet" ), true, false );
856 
857  for( auto fieldName : userFieldNames )
858  AddField( fieldName, true, false );
859 
860  // Add any templateFieldNames which aren't already present in the userFieldNames
861  for( auto templateFieldName : m_parent->GetTemplateFieldNames() )
862  if( userFieldNames.count( templateFieldName.m_Name ) == 0 )
863  AddField( templateFieldName.m_Name, false, false );
864 }
865 
866 
867 void DIALOG_FIELDS_EDITOR_GLOBAL::OnAddField( wxCommandEvent& event )
868 {
869  // quantities column will become new field column, so it needs to be reset
870  auto attr = new wxGridCellAttr;
871  m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
872  m_grid->SetColFormatCustom( m_dataModel->GetColsCount() - 1, wxGRID_VALUE_STRING );
873 
874  wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
875 
876  if( dlg.ShowModal() != wxID_OK )
877  return;
878 
879  wxString fieldName = dlg.GetValue();
880 
881  if( fieldName.IsEmpty() )
882  {
883  DisplayError( this, _( "Field must have a name." ) );
884  return;
885  }
886 
887  for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
888  {
889  if( fieldName == m_dataModel->GetColLabelValue( i ) )
890  {
891  DisplayError( this, wxString::Format( _( "Field name \"%s\" already in use." ), fieldName ) );
892  return;
893  }
894  }
895 
896  m_config->Write( "SymbolFieldEditor/Show/" + fieldName, true );
897 
898  AddField( fieldName, true, false );
899 
900  wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_INSERTED, m_fieldsCtrl->GetItemCount(), 1 );
901  m_grid->ProcessTableMessage( msg );
902 
903  // set up attributes on the new quantities column
904  attr = new wxGridCellAttr;
905  attr->SetReadOnly();
906  m_grid->SetColAttr( m_dataModel->GetColsCount() - 1, attr );
907  m_grid->SetColFormatNumber( m_dataModel->GetColsCount() - 1 );
908  m_grid->SetColSize( m_dataModel->GetColsCount() - 1, 50 );
909 }
910 
911 
913 {
914  wxDataViewItem item = event.GetItem();
915 
916  int row = m_fieldsCtrl->ItemToRow( item );
917  int col = event.GetColumn();
918 
919  switch ( col )
920  {
921  default:
922  break;
923 
924  case SHOW_FIELD_COLUMN:
925  {
926  bool value = m_fieldsCtrl->GetToggleValue( row, col );
927 
928  if( row == REFERENCE && !value )
929  {
930  DisplayError( this, _( "The Reference column cannot be hidden." ) );
931 
932  value = true;
933  m_fieldsCtrl->SetToggleValue( value, row, col );
934  }
935 
936  wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
937  m_config->Write( "SymbolFieldEditor/Show/" + fieldName, value );
938 
939  if( value )
940  m_grid->ShowCol( row );
941  else
942  m_grid->HideCol( row ); // grid's columns map to fieldsCtrl's rows
943  break;
944  }
945 
946  case GROUP_BY_COLUMN:
947  {
948  bool value = m_fieldsCtrl->GetToggleValue( row, col );
949  wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
950  m_config->Write( "SymbolFieldEditor/GroupBy/" + fieldName, value );
952  m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
953  m_grid->ForceRefresh();
954  break;
955  }
956  }
957 }
958 
959 
961 {
963  m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
964  m_grid->ForceRefresh();
965 }
966 
967 
968 void DIALOG_FIELDS_EDITOR_GLOBAL::OnColSort( wxGridEvent& aEvent )
969 {
970  int sortCol = aEvent.GetCol();
971  bool ascending;
972 
973  // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the
974  // event, and if we ask it will give us pre-event info.
975  if( m_grid->IsSortingBy( sortCol ) )
976  // same column; invert ascending
977  ascending = !m_grid->IsSortOrderAscending();
978  else
979  // different column; start with ascending
980  ascending = true;
981 
982  m_dataModel->Sort( sortCol, ascending );
983 }
984 
985 
987 {
988  m_grid->ForceRefresh();
989 }
990 
991 
993 {
995  m_dataModel->Sort( m_grid->GetSortingColumn(), m_grid->IsSortOrderAscending() );
996  m_grid->ForceRefresh();
997 }
998 
999 
1001 {
1002  if( event.GetCol() == REFERENCE )
1003  {
1004  m_grid->ClearSelection();
1005  m_grid->SetGridCursor( event.GetRow(), event.GetCol() );
1006 
1007  // Clear highligted symbols, if any
1008  m_parent->GetCanvas()->GetView()->HighlightItem( nullptr, nullptr );
1009  m_parent->GetCanvas()->Refresh();
1010 
1011  m_dataModel->ExpandCollapseRow( event.GetRow() );
1012  std::vector<SCH_REFERENCE> refs = m_dataModel->GetRowReferences( event.GetRow() );
1013 
1014  // Focus eeschema view on the component selected in the dialog
1015  if( refs.size() == 1 )
1016  {
1017  m_parent->FindComponentAndItem( refs[0].GetRef() + refs[0].GetRefNumber(),
1018  true, FIND_COMPONENT_ONLY, wxEmptyString );
1019  }
1020  }
1021  else
1022  {
1023  event.Skip();
1024  }
1025 }
1026 
1027 
1029 {
1030  /* TODO
1031  * - Option to select footprint if FOOTPRINT column selected
1032  */
1033 
1034  event.Skip();
1035 }
1036 
1037 
1039 {
1040  int nameColWidth = event.GetSize().GetX() - m_showColWidth - m_groupByColWidth - 8;
1041 
1042  // GTK loses its head and messes these up when resizing the splitter bar:
1043  m_fieldsCtrl->GetColumn( 1 )->SetWidth( m_showColWidth );
1044  m_fieldsCtrl->GetColumn( 2 )->SetWidth( m_groupByColWidth );
1045 
1046  m_fieldsCtrl->GetColumn( 0 )->SetWidth( nameColWidth );
1047 
1048  event.Skip();
1049 }
1050 
1051 
1053 {
1054  if( TransferDataFromWindow() )
1055  m_parent->SaveProject();
1056 }
1057 
1058 
1059 void DIALOG_FIELDS_EDITOR_GLOBAL::OnCancel( wxCommandEvent& event )
1060 {
1061  Close();
1062 }
1063 
1064 
1065 void DIALOG_FIELDS_EDITOR_GLOBAL::OnClose( wxCloseEvent& event )
1066 {
1067  // This is a cancel, so commit quietly as we're going to throw the results away anyway.
1068  m_grid->CommitPendingChanges( true );
1069 
1070  if( m_dataModel->IsEdited() )
1071  {
1072  if( !HandleUnsavedChanges( this, wxEmptyString,
1073  [&]()->bool { return TransferDataFromWindow(); } ) )
1074  {
1075  event.Veto();
1076  return;
1077  }
1078  }
1079 
1080  event.Skip();
1081 }
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:56
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:211
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.
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 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...
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)
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.
FIELDS_EDITOR_GRID_TRICKS(DIALOG_SHIM *aParent, WX_GRID *aGrid, wxDataViewListCtrl *aFieldsCtrl)
SCH_COMPONENT * GetComp() const
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:114
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
wxString EscapeString(const wxString &aSource)
These Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which ar...
Definition: string.cpp:43
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:300
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:500
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:207
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 UnescapeString(const wxString &aSource)
Definition: string.cpp:89
wxString GetName(bool aUseDefaultName=true) const
Function GetName returns the field name.
Definition: sch_field.cpp:435
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
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:243
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)
virtual void SetText(const wxString &aText)
Definition: eda_text.h:154
void AddField(const wxString &aName, bool defaultShow, bool defaultSortBy)