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