You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1565 lines
						
					
					
						
							38 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							1565 lines
						
					
					
						
							38 KiB
						
					
					
				
								/*
							 | 
						|
								* This program source code file is part of KiCad, a free EDA CAD application.
							 | 
						|
								 *
							 | 
						|
								 * Copyright (C) 2017 Oliver Walters
							 | 
						|
								 * Copyright (C) 2017 KiCad Developers, see CHANGELOG.TXT for contributors.
							 | 
						|
								 *
							 | 
						|
								 * This program is free software; you can redistribute it and/or
							 | 
						|
								 * modify it under the terms of the GNU General Public License
							 | 
						|
								 * as published by the Free Software Foundation; either version 2
							 | 
						|
								 * of the License, or (at your option) any later version.
							 | 
						|
								 *
							 | 
						|
								 * This program is distributed in the hope that it will be useful,
							 | 
						|
								 * but WITHOUT ANY WARRANTY; without even the implied warranty of
							 | 
						|
								 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
							 | 
						|
								 * GNU General Public License for more details.
							 | 
						|
								 *
							 | 
						|
								 * You should have received a copy of the GNU General Public License
							 | 
						|
								 * along with this program; if not, you may find one here:
							 | 
						|
								 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
							 | 
						|
								 * or you may search the http://www.gnu.org website for the version 2 license,
							 | 
						|
								 * or you may write to the Free Software Foundation, Inc.,
							 | 
						|
								 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
							 | 
						|
								 */
							 | 
						|
								
							 | 
						|
								#include "bom_table_model.h"
							 | 
						|
								
							 | 
						|
								// Indicator that multiple values exist in child rows
							 | 
						|
								#define ROW_MULT_ITEMS wxString( "<...>" )
							 | 
						|
								
							 | 
						|
								static const wxColor ROW_COLOUR_ITEM_CHANGED( 200, 0, 0 );
							 | 
						|
								static const wxColor ROW_COLOUR_MULTIPLE_ITEMS( 60, 90, 200 );
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Convert BOM_TABLE_ROW -> wxDataViewItem
							 | 
						|
								 */
							 | 
						|
								static wxDataViewItem RowToItem( BOM_TABLE_ROW const* aRow )
							 | 
						|
								{
							 | 
						|
								    return wxDataViewItem( const_cast<void*>( static_cast<void const*>( aRow ) ) );
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Convert wxDataViewItem -> BOM_TABEL_ROW
							 | 
						|
								 */
							 | 
						|
								static BOM_TABLE_ROW const* ItemToRow( wxDataViewItem aItem )
							 | 
						|
								{
							 | 
						|
								    if( !aItem.IsOk() )
							 | 
						|
								    {
							 | 
						|
								        return nullptr;
							 | 
						|
								    }
							 | 
						|
								    else
							 | 
						|
								    {
							 | 
						|
								        return static_cast<BOM_TABLE_ROW const*>( aItem.GetID() );
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								BOM_FIELD_VALUES::BOM_FIELD_VALUES( wxString aRefDes, FIELD_VALUE_MAP* aTemplate ) :
							 | 
						|
								        m_refDes( aRefDes ),
							 | 
						|
								        m_templateValues( aTemplate )
							 | 
						|
								{
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return the current value for the provided field ID
							 | 
						|
								 */
							 | 
						|
								bool BOM_FIELD_VALUES::GetFieldValue( unsigned int aFieldId, wxString& aValue ) const
							 | 
						|
								{
							 | 
						|
								    auto search = m_currentValues.find( aFieldId );
							 | 
						|
								
							 | 
						|
								    if( search == m_currentValues.end() )
							 | 
						|
								        return false;
							 | 
						|
								
							 | 
						|
								    aValue = search->second;
							 | 
						|
								
							 | 
						|
								    return true;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return the backup value for the provided field ID
							 | 
						|
								 */
							 | 
						|
								bool BOM_FIELD_VALUES::GetBackupValue( unsigned int aFieldId, wxString& aValue ) const
							 | 
						|
								{
							 | 
						|
								    auto search = m_backupValues.find( aFieldId );
							 | 
						|
								
							 | 
						|
								    if( search == m_backupValues.end() )
							 | 
						|
								        return false;
							 | 
						|
								
							 | 
						|
								    aValue = search->second;
							 | 
						|
								
							 | 
						|
								    return true;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return the template value for a provided field ID (if it exists)
							 | 
						|
								 */
							 | 
						|
								bool BOM_FIELD_VALUES::GetTemplateValue( unsigned int aFieldId, wxString& aValue ) const
							 | 
						|
								{
							 | 
						|
								    if( !m_templateValues )
							 | 
						|
								        return false;
							 | 
						|
								
							 | 
						|
								    auto search = m_templateValues->find( aFieldId );
							 | 
						|
								
							 | 
						|
								    if( search == m_templateValues->end() )
							 | 
						|
								        return false;
							 | 
						|
								
							 | 
						|
								    aValue = search->second;
							 | 
						|
								
							 | 
						|
								    return true;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Set the value for the provided field ID
							 | 
						|
								 * Field value is set under any of the following conditions:
							 | 
						|
								 * - param aOverwrite is true
							 | 
						|
								 * - There is no current value
							 | 
						|
								 * - The current value is empty
							 | 
						|
								 */
							 | 
						|
								void BOM_FIELD_VALUES::SetFieldValue( unsigned int aFieldId, wxString aValue, bool aOverwrite )
							 | 
						|
								{
							 | 
						|
								    if( aOverwrite || m_currentValues.count( aFieldId ) == 0 || m_currentValues[aFieldId].IsEmpty() )
							 | 
						|
								    {
							 | 
						|
								        m_currentValues[aFieldId] = aValue;
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								bool BOM_FIELD_VALUES::HasValueChanged( unsigned int aFieldId) const
							 | 
						|
								{
							 | 
						|
								    wxString currentValue, backupValue;
							 | 
						|
								
							 | 
						|
								    GetFieldValue( aFieldId, currentValue );
							 | 
						|
								    GetBackupValue( aFieldId, backupValue );
							 | 
						|
								
							 | 
						|
								    return currentValue.Cmp( backupValue ) != 0;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								void BOM_FIELD_VALUES::RevertChanges( unsigned int aFieldId )
							 | 
						|
								{
							 | 
						|
								    wxString backupValue;
							 | 
						|
								
							 | 
						|
								    GetBackupValue( aFieldId, backupValue );
							 | 
						|
								
							 | 
						|
								    SetFieldValue( aFieldId, backupValue, true );
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								void BOM_FIELD_VALUES::SetBackupPoint()
							 | 
						|
								{
							 | 
						|
								    for( auto it = m_currentValues.begin(); it != m_currentValues.end(); ++it )
							 | 
						|
								    {
							 | 
						|
								        m_backupValues[it->first] = it->second;
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								BOM_TABLE_ROW::BOM_TABLE_ROW() : m_columnList( nullptr )
							 | 
						|
								{
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Update cell attributes based on parameters of the cell
							 | 
						|
								 * Default implementation highlights cells that have been altered
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_ROW::GetAttr( unsigned int aFieldId, wxDataViewItemAttr& aAttr ) const
							 | 
						|
								{
							 | 
						|
								    auto field = m_columnList->GetColumnById( aFieldId );
							 | 
						|
								
							 | 
						|
								    if( HasValueChanged( field ) )
							 | 
						|
								    {
							 | 
						|
								        aAttr.SetBold( true );
							 | 
						|
								        aAttr.SetItalic( true );
							 | 
						|
								        aAttr.SetColour( ROW_COLOUR_ITEM_CHANGED );
							 | 
						|
								        return true;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return false;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								bool BOM_TABLE_ROW::HasChanged() const
							 | 
						|
								{
							 | 
						|
								    if( !m_columnList )
							 | 
						|
								        return false;
							 | 
						|
								
							 | 
						|
								    for( auto& column : m_columnList->Columns )
							 | 
						|
								    {
							 | 
						|
								        if( column && HasValueChanged( column ) )
							 | 
						|
								        {
							 | 
						|
								            return true;
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return false;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Create a new group (which contains one or more components)
							 | 
						|
								 */
							 | 
						|
								BOM_TABLE_GROUP::BOM_TABLE_GROUP( BOM_COLUMN_LIST* aColumnList )
							 | 
						|
								{
							 | 
						|
								    m_columnList = aColumnList;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								bool BOM_TABLE_GROUP::GetAttr( unsigned int aFieldId, wxDataViewItemAttr& aAttr ) const
							 | 
						|
								{
							 | 
						|
								    if( GetFieldValue( aFieldId ).Cmp( ROW_MULT_ITEMS ) == 0 )
							 | 
						|
								    {
							 | 
						|
								        aAttr.SetItalic( true );
							 | 
						|
								        aAttr.SetColour( ROW_COLOUR_MULTIPLE_ITEMS );
							 | 
						|
								        return true;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return BOM_TABLE_ROW::GetAttr( aFieldId, aAttr );
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return the value associated with a given field in the group.
							 | 
						|
								 * Some fields require special attention.
							 | 
						|
								 */
							 | 
						|
								wxString BOM_TABLE_GROUP::GetFieldValue( unsigned int aFieldId ) const
							 | 
						|
								{
							 | 
						|
								    wxString value;
							 | 
						|
								
							 | 
						|
								    // Account for special cases
							 | 
						|
								    switch( aFieldId )
							 | 
						|
								    {
							 | 
						|
								    // QUANTITY returns the size of the group
							 | 
						|
								    case BOM_COL_ID_QUANTITY:
							 | 
						|
								        value = wxString::Format( "%u", (unsigned int) GroupSize() );
							 | 
						|
								        break;
							 | 
						|
								    // REFERENCE field returns consolidated list of references
							 | 
						|
								    case BOM_COL_ID_REFERENCE:
							 | 
						|
								        value = wxJoin( GetReferences(), ' ' );
							 | 
						|
								        break;
							 | 
						|
								    // Otherwise, return component data
							 | 
						|
								    default:
							 | 
						|
								        if( Components.size() == 0 )
							 | 
						|
								        {
							 | 
						|
								            value = wxEmptyString;
							 | 
						|
								        }
							 | 
						|
								        else
							 | 
						|
								        {
							 | 
						|
								            // If the components in this group contain multiple items,
							 | 
						|
								            // display a special string indicating this
							 | 
						|
								            for( unsigned int i=0; i<Components.size(); i++ )
							 | 
						|
								            {
							 | 
						|
								                auto const& cmp = Components[i];
							 | 
						|
								
							 | 
						|
								                if( i == 0 )
							 | 
						|
								                {
							 | 
						|
								                    value = cmp->GetFieldValue( aFieldId );
							 | 
						|
								                }
							 | 
						|
								                // Mismatch found
							 | 
						|
								                else if( value.Cmp( cmp->GetFieldValue( aFieldId ) ) != 0 )
							 | 
						|
								                {
							 | 
						|
								                    value = ROW_MULT_ITEMS;
							 | 
						|
								                    break;
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								        break;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return value;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Set the value of a field in a group
							 | 
						|
								 * The new value is pushed to all components that are children of this group
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_GROUP::SetFieldValue( unsigned int aFieldId, const wxString aValue, bool aOverwrite )
							 | 
						|
								{
							 | 
						|
								    bool result = false;
							 | 
						|
								
							 | 
						|
								    for( auto& cmp : Components )
							 | 
						|
								    {
							 | 
						|
								        result |= cmp->SetFieldValue( aFieldId, aValue, aOverwrite );
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return result;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Determines if a given component matches against a particular field.
							 | 
						|
								 *
							 | 
						|
								 * - Tests each field in turn; all fields must match
							 | 
						|
								 * - Some fields require special checking
							 | 
						|
								 *
							 | 
						|
								 * @param aField - The field to test
							 | 
						|
								 * @param aComponent - The component being tested
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_GROUP::TestField( BOM_COLUMN* aField, BOM_TABLE_COMPONENT* aComponent ) const
							 | 
						|
								{
							 | 
						|
								    if( !aField || !aComponent )
							 | 
						|
								        return false;
							 | 
						|
								
							 | 
						|
								    if( Components.size() == 0 )
							 | 
						|
								        return true;
							 | 
						|
								
							 | 
						|
								    wxString componentValue;
							 | 
						|
								    wxString comparisonValue;
							 | 
						|
								
							 | 
						|
								    // Some fields are handled in a special manner
							 | 
						|
								    // (handle these first)
							 | 
						|
								    switch( aField->Id() )
							 | 
						|
								    {
							 | 
						|
								    // These fields should NOT be compared (return True)
							 | 
						|
								    case BOM_COL_ID_QUANTITY:
							 | 
						|
								        return true;
							 | 
						|
								    // Reference matching is done only on prefix
							 | 
						|
								    case BOM_COL_ID_REFERENCE:
							 | 
						|
								        componentValue = aComponent->GetPrefix();
							 | 
						|
								        comparisonValue = Components[0]->GetPrefix();
							 | 
						|
								        break;
							 | 
						|
								    default:
							 | 
						|
								        componentValue = aComponent->GetFieldValue( aField->Id() );
							 | 
						|
								        comparisonValue = Components[0]->GetFieldValue( aField->Id() );
							 | 
						|
								        break;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    bool result = componentValue.Cmp( comparisonValue ) == 0;
							 | 
						|
								
							 | 
						|
								    return result;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Add a new component to the group.
							 | 
						|
								 * It is assumed at this stage that the component is a good match for the group.
							 | 
						|
								 * @param aComponent is the new component to add
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_GROUP::AddComponent( BOM_TABLE_COMPONENT* aComponent )
							 | 
						|
								{
							 | 
						|
								    if( !aComponent )
							 | 
						|
								        return false;
							 | 
						|
								
							 | 
						|
								    // To be a match, all fields must match!
							 | 
						|
								    bool match = true;
							 | 
						|
								
							 | 
						|
								    for( auto* column : m_columnList->Columns )
							 | 
						|
								    {
							 | 
						|
								        // Ignore any columns marked as "not used for sorting"
							 | 
						|
								        if( !column->IsUsedToSort() )
							 | 
						|
								            continue;
							 | 
						|
								
							 | 
						|
								        match = TestField( column, aComponent );
							 | 
						|
								
							 | 
						|
								        // Escape on first mismatch
							 | 
						|
								        if( !match )
							 | 
						|
								        {
							 | 
						|
								            break;
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    if( match )
							 | 
						|
								    {
							 | 
						|
								        aComponent->SetParent( this );
							 | 
						|
								        Components.push_back( aComponent );
							 | 
						|
								        return true;
							 | 
						|
								    }
							 | 
						|
								    else
							 | 
						|
								    {
							 | 
						|
								        return false;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return false;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Adds each child row to the supplied list, and returns the total child count
							 | 
						|
								 */
							 | 
						|
								unsigned int BOM_TABLE_GROUP::GetChildren( wxDataViewItemArray& aChildren ) const
							 | 
						|
								{
							 | 
						|
								    // Show drop-down for child components
							 | 
						|
								    for( auto& row : Components )
							 | 
						|
								    {
							 | 
						|
								        if( row )
							 | 
						|
								        {
							 | 
						|
								            aChildren.push_back( RowToItem( &*row ) );
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return aChildren.size();
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Test if any components in this group have a new value in the provided field
							 | 
						|
								 * @param aField is the field to test
							 | 
						|
								 * @return true if any children have changed else false
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_GROUP::HasValueChanged( BOM_COLUMN* aField ) const
							 | 
						|
								{
							 | 
						|
								
							 | 
						|
								    bool changed = false;
							 | 
						|
								
							 | 
						|
								    if( !aField )
							 | 
						|
								        return false;
							 | 
						|
								
							 | 
						|
								    for( auto const& row : Components )
							 | 
						|
								    {
							 | 
						|
								        if( row->HasValueChanged( aField ) )
							 | 
						|
								        {
							 | 
						|
								            changed = true;
							 | 
						|
								            break;
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // If the value has changed, the group field data must be updated
							 | 
						|
								    if( changed )
							 | 
						|
								    {
							 | 
						|
								        //TODO
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return changed;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return a list of (ordered) references
							 | 
						|
								 * for all the components in this group
							 | 
						|
								 *
							 | 
						|
								 * @param aSort - Sort the references
							 | 
						|
								 */
							 | 
						|
								wxArrayString BOM_TABLE_GROUP::GetReferences( bool aSort ) const
							 | 
						|
								{
							 | 
						|
								    wxArrayString refs;
							 | 
						|
								
							 | 
						|
								    for( auto const& cmp : Components )
							 | 
						|
								    {
							 | 
						|
								        if( cmp )
							 | 
						|
								        {
							 | 
						|
								            refs.Add( cmp->GetFieldValue( BOM_COL_ID_REFERENCE ) );
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    if( aSort )
							 | 
						|
								    {
							 | 
						|
								        refs.Sort( SortReferences );
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return refs;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Compare two references (e.g. "R100", "R19") and perform a 'natural' sort
							 | 
						|
								 * This sorting must preserve numerical order rather than alphabetical
							 | 
						|
								 * e.g. "R100" is lower (alphabetically) than "R19"
							 | 
						|
								 * BUT should be placed after R19
							 | 
						|
								 */
							 | 
						|
								int BOM_TABLE_GROUP::SortReferences( const wxString& aFirst, const wxString& aSecond )
							 | 
						|
								{
							 | 
						|
								    // Default sorting
							 | 
						|
								    int defaultSort = aFirst.Cmp( aSecond );
							 | 
						|
								
							 | 
						|
								    static const wxString REGEX_STRING = "^([a-zA-Z]+)(\\d+)$";
							 | 
						|
								
							 | 
						|
								    // Compile regex statically
							 | 
						|
								    static wxRegEx regexFirst( REGEX_STRING, wxRE_ICASE | wxRE_ADVANCED );
							 | 
						|
								    static wxRegEx regexSecond( REGEX_STRING, wxRE_ICASE | wxRE_ADVANCED );
							 | 
						|
								
							 | 
						|
								    if( !regexFirst.Matches( aFirst ) || !regexSecond.Matches( aSecond ) )
							 | 
						|
								    {
							 | 
						|
								        return defaultSort;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // First priority is to order by prefix
							 | 
						|
								    wxString prefixFirst  = regexFirst.GetMatch( aFirst, 1 );
							 | 
						|
								    wxString prefixSecond = regexSecond.GetMatch( aSecond, 1 );
							 | 
						|
								
							 | 
						|
								    if( prefixFirst.CmpNoCase( prefixSecond ) != 0 ) // Different prefixes!
							 | 
						|
								    {
							 | 
						|
								        return defaultSort;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    wxString numStrFirst   = regexFirst.GetMatch( aFirst, 2 );
							 | 
						|
								    wxString numStrSecond  = regexSecond.GetMatch( aSecond, 2 );
							 | 
						|
								
							 | 
						|
								    // If either match failed, just return normal string comparison
							 | 
						|
								    if( numStrFirst.IsEmpty() || numStrSecond.IsEmpty() )
							 | 
						|
								    {
							 | 
						|
								        return defaultSort;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Convert each number string to an integer
							 | 
						|
								    long numFirst    = 0;
							 | 
						|
								    long numSecond   = 0;
							 | 
						|
								
							 | 
						|
								    // If either conversion fails, return normal string comparison
							 | 
						|
								    if( !numStrFirst.ToLong( &numFirst ) || !numStrSecond.ToLong( &numSecond ) )
							 | 
						|
								    {
							 | 
						|
								        return defaultSort;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return (int) (numFirst - numSecond);
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Compare two VALUE fields.
							 | 
						|
								 * A value field can reasonably be expected to be one of:
							 | 
						|
								 * a) Purely numerical e.g. '22'
							 | 
						|
								 * b) Numerical with included units e.g. '15uF'
							 | 
						|
								 * c) Numerical with included prefix but no units e.g. '20n'
							 | 
						|
								 * d) Numerical with prefix inside number e.g. '4K7'
							 | 
						|
								 * e) Other, e.g. 'MAX232'
							 | 
						|
								 *
							 | 
						|
								 * Cases a) to d) should be detected and converted to a common representation
							 | 
						|
								 * Values that do not match this pattern should revert to standard string comparison
							 | 
						|
								 */
							 | 
						|
								int BOM_TABLE_GROUP::SortValues( const wxString& aFirst, const wxString& aSecond )
							 | 
						|
								{
							 | 
						|
								    //TODO - Intelligent comparison of component values
							 | 
						|
								    // e.g. 4K > 499
							 | 
						|
								    // e.g. 1nF < 0.1u
							 | 
						|
								
							 | 
						|
								    // For now, just return default comparison
							 | 
						|
								
							 | 
						|
								    return aFirst.CmpNoCase( aSecond );
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Create a new COMPONENT row
							 | 
						|
								 * Each COMPONENT row is associated with a single component item.
							 | 
						|
								 */
							 | 
						|
								BOM_TABLE_COMPONENT::BOM_TABLE_COMPONENT( BOM_TABLE_GROUP* aParent,
							 | 
						|
								                                          BOM_COLUMN_LIST* aColumnList,
							 | 
						|
								                                          BOM_FIELD_VALUES* aFieldValues )
							 | 
						|
								{
							 | 
						|
								    m_parent = aParent;
							 | 
						|
								    m_columnList = aColumnList;
							 | 
						|
								    m_fieldValues = aFieldValues;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Try to add a unit to this component
							 | 
						|
								 * If the references match, it will be added
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_COMPONENT::AddUnit( SCH_REFERENCE aUnit )
							 | 
						|
								{
							 | 
						|
								    // Addition is successful if the references match or there are currently no units in the group
							 | 
						|
								    if( Units.size() == 0  || Units[0].GetRef().Cmp( aUnit.GetRef() ) == 0 )
							 | 
						|
								    {
							 | 
						|
								        Units.push_back( aUnit );
							 | 
						|
								
							 | 
						|
								        wxString value;
							 | 
						|
								
							 | 
						|
								        // Extract the component data
							 | 
						|
								        for( auto column : m_columnList->Columns )
							 | 
						|
								        {
							 | 
						|
								            auto cmp = aUnit.GetComp();
							 | 
						|
								
							 | 
						|
								            switch( column->Id() )
							 | 
						|
								            {
							 | 
						|
								            case BOM_COL_ID_QUANTITY:
							 | 
						|
								                value = wxEmptyString;
							 | 
						|
								                break;
							 | 
						|
								            case BOM_COL_ID_DESCRIPTION:
							 | 
						|
								                value = cmp->GetAliasDescription();
							 | 
						|
								                break;
							 | 
						|
								            case BOM_COL_ID_DATASHEET:
							 | 
						|
								                value = cmp->GetField( DATASHEET )->GetText();
							 | 
						|
								                if( value.IsEmpty() )
							 | 
						|
								                {
							 | 
						|
								                    value = cmp->GetAliasDocumentation();
							 | 
						|
								                }
							 | 
						|
								                break;
							 | 
						|
								            case BOM_COL_ID_REFERENCE:
							 | 
						|
								                value = aUnit.GetRef();
							 | 
						|
								                break;
							 | 
						|
								            case BOM_COL_ID_VALUE:
							 | 
						|
								                value = cmp->GetField( VALUE )->GetText();
							 | 
						|
								                break;
							 | 
						|
								            case BOM_COL_ID_FOOTPRINT:
							 | 
						|
								                value = cmp->GetField( FOOTPRINT )->GetText();
							 | 
						|
								                break;
							 | 
						|
								
							 | 
						|
								            // User fields
							 | 
						|
								            default:
							 | 
						|
								                value = cmp->GetFieldText( column->Title(), false );
							 | 
						|
								                break;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            m_fieldValues->SetFieldValue( column->Id(), value );
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        return true;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return false;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return the value associated with a particular field
							 | 
						|
								 * If no field is found, return an empty string
							 | 
						|
								 */
							 | 
						|
								wxString BOM_TABLE_COMPONENT::GetFieldValue( unsigned int aFieldId ) const
							 | 
						|
								{
							 | 
						|
								    wxString value;
							 | 
						|
								
							 | 
						|
								    switch ( aFieldId )
							 | 
						|
								    {
							 | 
						|
								    case BOM_COL_ID_QUANTITY:
							 | 
						|
								        return wxEmptyString;
							 | 
						|
								    case BOM_COL_ID_REFERENCE:
							 | 
						|
								        return GetReference();
							 | 
						|
								    default:
							 | 
						|
								        break;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    if( m_fieldValues )
							 | 
						|
								    {
							 | 
						|
								        m_fieldValues->GetFieldValue( aFieldId, value );
							 | 
						|
								
							 | 
						|
								        if( value.IsEmpty() )
							 | 
						|
								        {
							 | 
						|
								            m_fieldValues->GetTemplateValue( aFieldId, value );
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return value;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Set the value of a field in the component
							 | 
						|
								 * @param aFieldId is the unique ID of the field to update
							 | 
						|
								 * @param aValue is the new value
							 | 
						|
								 * @param aOverwrite enforces writing even if a value exists
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_COMPONENT::SetFieldValue( unsigned int aFieldId, const wxString aValue, bool aOverwrite )
							 | 
						|
								{
							 | 
						|
								    if( m_fieldValues )
							 | 
						|
								    {
							 | 
						|
								        m_fieldValues->SetFieldValue( aFieldId, aValue, aOverwrite );
							 | 
						|
								        return true;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return false;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return the prefix of a component e.g. "R23" -> "R"
							 | 
						|
								 */
							 | 
						|
								wxString BOM_TABLE_COMPONENT::GetPrefix() const
							 | 
						|
								{
							 | 
						|
								    if( Units.size() == 0 )
							 | 
						|
								        return wxEmptyString;
							 | 
						|
								
							 | 
						|
								    return Units[0].GetComp()->GetPrefix();
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return the reference of a component e.g. "R23"
							 | 
						|
								 */
							 | 
						|
								wxString BOM_TABLE_COMPONENT::GetReference() const
							 | 
						|
								{
							 | 
						|
								    if( Units.size() == 0 )
							 | 
						|
								        return wxEmptyString;
							 | 
						|
								
							 | 
						|
								    return Units[0].GetRef();
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Determines if the given field has been changed for this component
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_COMPONENT::HasValueChanged( BOM_COLUMN* aField ) const
							 | 
						|
								{
							 | 
						|
								    if( !aField )
							 | 
						|
								    {
							 | 
						|
								        return false;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return m_fieldValues->HasValueChanged( aField->Id() );
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * If any changes have been made to this component,
							 | 
						|
								 * they are now applied to the schematic component
							 | 
						|
								 */
							 | 
						|
								void BOM_TABLE_COMPONENT::ApplyFieldChanges()
							 | 
						|
								{
							 | 
						|
								    for( auto& unit : Units )
							 | 
						|
								    {
							 | 
						|
								        auto cmp = unit.GetComp();
							 | 
						|
								
							 | 
						|
								        if( !cmp )
							 | 
						|
								            continue;
							 | 
						|
								
							 | 
						|
								        // Iterate over each column
							 | 
						|
								        SCH_FIELD* field;
							 | 
						|
								
							 | 
						|
								        for( auto& column : m_columnList->Columns )
							 | 
						|
								        {
							 | 
						|
								            if( column && HasValueChanged( column ) )
							 | 
						|
								            {
							 | 
						|
								                wxString value = GetFieldValue( column->Id() );
							 | 
						|
								
							 | 
						|
								                switch( column->Id() )
							 | 
						|
								                {
							 | 
						|
								                // Ignore read-only fields
							 | 
						|
								                case BOM_COL_ID_REFERENCE:
							 | 
						|
								                case BOM_COL_ID_QUANTITY:
							 | 
						|
								                    continue;
							 | 
						|
								                // Special field considerations
							 | 
						|
								                case BOM_COL_ID_FOOTPRINT:
							 | 
						|
								                    field = cmp->GetField( FOOTPRINT );
							 | 
						|
								                    break;
							 | 
						|
								                case BOM_COL_ID_VALUE:
							 | 
						|
								                    field = cmp->GetField( VALUE );
							 | 
						|
								                    break;
							 | 
						|
								                case BOM_COL_ID_DATASHEET:
							 | 
						|
								                    field = cmp->GetField( DATASHEET );
							 | 
						|
								                    break;
							 | 
						|
								                default:
							 | 
						|
								                    // Find the field by name (but ignore default fields)
							 | 
						|
								                    field = cmp->FindField( column->Title(), false );
							 | 
						|
								                    break;
							 | 
						|
								                }
							 | 
						|
								
							 | 
						|
								                // New field needs to be added?
							 | 
						|
								                if( !field && !value.IsEmpty() )
							 | 
						|
								                {
							 | 
						|
								                    SCH_FIELD newField( wxPoint( 0, 0 ), -1, cmp, column->Title() );
							 | 
						|
								                    field = cmp->AddField( newField );
							 | 
						|
								                }
							 | 
						|
								
							 | 
						|
								                if( field )
							 | 
						|
								                {
							 | 
						|
								                    field->SetText( value );
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Revert the displayed fields for this component
							 | 
						|
								 * to their original values (matching the schematic data)
							 | 
						|
								 */
							 | 
						|
								void BOM_TABLE_COMPONENT::RevertFieldChanges()
							 | 
						|
								{
							 | 
						|
								    for( auto& column : m_columnList->Columns )
							 | 
						|
								    {
							 | 
						|
								        switch( column->Id() )
							 | 
						|
								        {
							 | 
						|
								        case BOM_COL_ID_REFERENCE:
							 | 
						|
								        case BOM_COL_ID_QUANTITY:
							 | 
						|
								            continue;
							 | 
						|
								        default:
							 | 
						|
								            break;
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        m_fieldValues->RevertChanges( column->Id() );
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								BOM_TABLE_MODEL::BOM_TABLE_MODEL() :
							 | 
						|
								        m_widget( nullptr ),
							 | 
						|
								        m_sortingColumn( BOM_COL_ID_REFERENCE ),
							 | 
						|
								        m_sortingOrder( true )
							 | 
						|
								{
							 | 
						|
								    //TODO
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Create a container for the BOM_TABLE_MODEL
							 | 
						|
								 * This is required for reference counting by wxDataViewCtrl
							 | 
						|
								 */
							 | 
						|
								BOM_TABLE_MODEL::MODEL_PTR BOM_TABLE_MODEL::Create()
							 | 
						|
								{
							 | 
						|
								    auto model = new BOM_TABLE_MODEL();
							 | 
						|
								
							 | 
						|
								    auto container = BOM_TABLE_MODEL::MODEL_PTR( model );
							 | 
						|
								
							 | 
						|
								    return container;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								BOM_TABLE_MODEL::~BOM_TABLE_MODEL()
							 | 
						|
								{
							 | 
						|
								   //TODO
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								wxDataViewColumn* BOM_TABLE_MODEL::AddColumn( BOM_COLUMN* aColumn, int aPosition )
							 | 
						|
								{
							 | 
						|
								    static const unsigned int flags = wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE;
							 | 
						|
								
							 | 
						|
								    if( !m_widget || !aColumn || !aColumn->IsVisible() )
							 | 
						|
								        return nullptr;
							 | 
						|
								
							 | 
						|
								    wxDataViewCellMode editFlag = aColumn->IsReadOnly() ? wxDATAVIEW_CELL_INERT : wxDATAVIEW_CELL_EDITABLE;
							 | 
						|
								
							 | 
						|
								    auto renderer = new wxDataViewTextRenderer( "string" , editFlag );
							 | 
						|
								
							 | 
						|
								    auto column = new wxDataViewColumn( aColumn->Title(),
							 | 
						|
								                                        renderer,
							 | 
						|
								                                        aColumn->Id(),
							 | 
						|
								                                        150, //TODO - variable default width?
							 | 
						|
								                                        wxAlignment( wxALIGN_LEFT ),
							 | 
						|
								                                        flags );
							 | 
						|
								
							 | 
						|
								    // Work out where to insert the column
							 | 
						|
								    std::set<unsigned int> columnsBefore;
							 | 
						|
								
							 | 
						|
								    for( auto testCol : ColumnList.Columns )
							 | 
						|
								    {
							 | 
						|
								        if( testCol->Id() == aColumn->Id() )
							 | 
						|
								        {
							 | 
						|
								            break;
							 | 
						|
								        }
							 | 
						|
								        else
							 | 
						|
								        {
							 | 
						|
								            columnsBefore.insert( testCol->Id() );
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    bool found = false;
							 | 
						|
								
							 | 
						|
								    for( unsigned int ii=0; ii<m_widget->GetColumnCount(); ii++ )
							 | 
						|
								    {
							 | 
						|
								        auto col = m_widget->GetColumn( ii );
							 | 
						|
								
							 | 
						|
								        if( !col )
							 | 
						|
								            continue;
							 | 
						|
								
							 | 
						|
								        // If the new column is already in the view, escape
							 | 
						|
								        if( col->GetModelColumn() == aColumn->Id() )
							 | 
						|
								        {
							 | 
						|
								            return col;
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        // If we should insert the new column BEFORE this one
							 | 
						|
								        if( columnsBefore.count( col->GetModelColumn() ) == 0 )
							 | 
						|
								        {
							 | 
						|
								            found = true;
							 | 
						|
								            m_widget->InsertColumn( ii, column );
							 | 
						|
								            break;
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    if( !found )
							 | 
						|
								        m_widget->AppendColumn( column );
							 | 
						|
								
							 | 
						|
								    column->SetResizeable( true );
							 | 
						|
								
							 | 
						|
								    return column;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Gracefully remove the given column from the wxDataViewCtrl
							 | 
						|
								 * Removing columns individually prevents bad redraw of entire table
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_MODEL::RemoveColumn( BOM_COLUMN* aColumn )
							 | 
						|
								{
							 | 
						|
								    if( !m_widget || !aColumn )
							 | 
						|
								        return false;
							 | 
						|
								
							 | 
						|
								    for( unsigned int ii=0; ii<m_widget->GetColumnCount(); ii++ )
							 | 
						|
								    {
							 | 
						|
								        auto col = m_widget->GetColumn( ii );
							 | 
						|
								
							 | 
						|
								        if( col && col->GetModelColumn() == aColumn->Id() )
							 | 
						|
								        {
							 | 
						|
								            m_widget->DeleteColumn( col );
							 | 
						|
								            return true;
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return false;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Attach the MODEL to a particular VIEW
							 | 
						|
								 * This function causes the view to be updated appropriately
							 | 
						|
								 */
							 | 
						|
								void BOM_TABLE_MODEL::AttachTo( wxDataViewCtrl* aView )
							 | 
						|
								{
							 | 
						|
								    if( !aView )
							 | 
						|
								    {
							 | 
						|
								        return;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    m_widget = aView;
							 | 
						|
								    aView->Freeze();
							 | 
						|
								    Cleared();
							 | 
						|
								
							 | 
						|
								    aView->AssociateModel( this );
							 | 
						|
								    aView->ClearColumns();
							 | 
						|
								
							 | 
						|
								    // Add all columns
							 | 
						|
								    for( auto col : ColumnList.Columns )
							 | 
						|
								    {
							 | 
						|
								        AddColumn( col );
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    aView->Thaw();
							 | 
						|
								
							 | 
						|
								    // Notify the view that the data needs to be redrawn
							 | 
						|
								    aView->Update();
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return the total number of components displayed by the model
							 | 
						|
								 */
							 | 
						|
								unsigned int BOM_TABLE_MODEL::ComponentCount() const
							 | 
						|
								{
							 | 
						|
								    unsigned int count = 0;
							 | 
						|
								
							 | 
						|
								    for( auto& group : Groups )
							 | 
						|
								    {
							 | 
						|
								        if( group )
							 | 
						|
								            count += group->GroupSize();
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return count;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								void BOM_TABLE_MODEL::ClearColumns()
							 | 
						|
								{
							 | 
						|
								    ColumnList.Clear();
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Add default columns to the table
							 | 
						|
								 * These columns are ALWAYS available in the table
							 | 
						|
								 * They are immutable - can be hidden by user but not removed
							 | 
						|
								 */
							 | 
						|
								void BOM_TABLE_MODEL::AddDefaultColumns()
							 | 
						|
								{
							 | 
						|
								    // Reference column is read-only
							 | 
						|
								    ColumnList.AddColumn( new BOM_COLUMN(
							 | 
						|
								                BOM_COL_ID_REFERENCE,
							 | 
						|
								                BOM_COL_TYPE_GENERATED,
							 | 
						|
								                BOM_COL_TITLE_REFERENCE,
							 | 
						|
								                true, true ) );
							 | 
						|
								
							 | 
						|
								    ColumnList.AddColumn( new BOM_COLUMN(
							 | 
						|
								               BOM_COL_ID_VALUE,
							 | 
						|
								               BOM_COL_TYPE_KICAD,
							 | 
						|
								               BOM_COL_TITLE_VALUE,
							 | 
						|
								               true, false ) );
							 | 
						|
								
							 | 
						|
								    ColumnList.AddColumn( new BOM_COLUMN(
							 | 
						|
								               BOM_COL_ID_FOOTPRINT,
							 | 
						|
								               BOM_COL_TYPE_KICAD,
							 | 
						|
								               BOM_COL_TITLE_FOOTPRINT,
							 | 
						|
								               true, false ) );
							 | 
						|
								
							 | 
						|
								    ColumnList.AddColumn( new BOM_COLUMN(
							 | 
						|
								               BOM_COL_ID_DATASHEET,
							 | 
						|
								               BOM_COL_TYPE_KICAD,
							 | 
						|
								               BOM_COL_TITLE_DATASHEET,
							 | 
						|
								               true, false ) );
							 | 
						|
								
							 | 
						|
								    // Description comes from .dcm file and is read-only
							 | 
						|
								    ColumnList.AddColumn( new BOM_COLUMN(
							 | 
						|
								               BOM_COL_ID_DESCRIPTION,
							 | 
						|
								               BOM_COL_TYPE_LIBRARY,
							 | 
						|
								               BOM_COL_TITLE_DESCRIPTION,
							 | 
						|
								               true, true ) );
							 | 
						|
								
							 | 
						|
								    // Quantity column is read-only
							 | 
						|
								    ColumnList.AddColumn( new BOM_COLUMN(
							 | 
						|
								               BOM_COL_ID_QUANTITY,
							 | 
						|
								               BOM_COL_TYPE_GENERATED,
							 | 
						|
								               BOM_COL_TITLE_QUANTITY,
							 | 
						|
								               true, true ) );
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Extract field data from all components
							 | 
						|
								 * Compiles an inclusive list of all field names from all components
							 | 
						|
								 */
							 | 
						|
								void BOM_TABLE_MODEL::AddComponentFields( SCH_COMPONENT* aCmp )
							 | 
						|
								{
							 | 
						|
								    std::vector< SCH_FIELD* > fields;
							 | 
						|
								
							 | 
						|
								    SCH_FIELD* field;
							 | 
						|
								    wxString fieldName;
							 | 
						|
								
							 | 
						|
								    if( nullptr == aCmp )
							 | 
						|
								        return;
							 | 
						|
								
							 | 
						|
								    // Extract custom columns from component
							 | 
						|
								    fields.clear();
							 | 
						|
								    aCmp->GetFields( fields, false );
							 | 
						|
								
							 | 
						|
								    // Iterate over custom field datas
							 | 
						|
								    for( unsigned int i=MANDATORY_FIELDS; i<fields.size(); i++ )
							 | 
						|
								    {
							 | 
						|
								        field = fields[i];
							 | 
						|
								
							 | 
						|
								        if( nullptr == field ) continue;
							 | 
						|
								
							 | 
						|
								        fieldName = field->GetName();
							 | 
						|
								
							 | 
						|
								        bool userMatchFound = false;
							 | 
						|
								
							 | 
						|
								        // Search for the column within the existing columns
							 | 
						|
								        for( auto col : ColumnList.Columns )
							 | 
						|
								        {
							 | 
						|
								            if( !col )
							 | 
						|
								            {
							 | 
						|
								                continue;
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            if( col->Title().Cmp( fieldName ) == 0 )
							 | 
						|
								            {
							 | 
						|
								                if( col->Id() >= BOM_COL_ID_USER )
							 | 
						|
								                {
							 | 
						|
								                    userMatchFound = true;
							 | 
						|
								                    break;
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        // If a user-made column already exists with the same name, abort
							 | 
						|
								        if( userMatchFound )
							 | 
						|
								        {
							 | 
						|
								            continue;
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        ColumnList.AddColumn( new BOM_COLUMN( ColumnList.NextFieldId(),
							 | 
						|
								                                              BOM_COL_TYPE_USER,
							 | 
						|
								                                              field->GetName(),
							 | 
						|
								                                              true, false ) );
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Add a list of component items to the BOM manager
							 | 
						|
								 * Creates consolidated groups of components as required
							 | 
						|
								 */
							 | 
						|
								void BOM_TABLE_MODEL::SetComponents( SCH_REFERENCE_LIST aRefs, const TEMPLATE_FIELDNAMES& aTemplateFields )
							 | 
						|
								{
							 | 
						|
								
							 | 
						|
								    // Add default columns
							 | 
						|
								    AddDefaultColumns();
							 | 
						|
								
							 | 
						|
								    // Extract all component fields
							 | 
						|
								    for( unsigned int ii=0; ii<aRefs.GetCount(); ii++ )
							 | 
						|
								    {
							 | 
						|
								        auto ref = aRefs.GetItem( ii );
							 | 
						|
								        auto cmp = ref.GetComp();
							 | 
						|
								
							 | 
						|
								        if( cmp )
							 | 
						|
								        {
							 | 
						|
								            AddComponentFields( cmp );
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Add template fields if they are not already added
							 | 
						|
								    for( auto field : aTemplateFields )
							 | 
						|
								    {
							 | 
						|
								        BOM_COLUMN* col;
							 | 
						|
								
							 | 
						|
								        col = ColumnList.GetColumnByTitle( field.m_Name );
							 | 
						|
								
							 | 
						|
								        if( !col )
							 | 
						|
								        {
							 | 
						|
								            col = new BOM_COLUMN( ColumnList.NextFieldId(),
							 | 
						|
								                                       BOM_COL_TYPE_USER,
							 | 
						|
								                                       field.m_Name,
							 | 
						|
								                                       true, false );
							 | 
						|
								
							 | 
						|
								            ColumnList.AddColumn( col );
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        // Add template value for that field
							 | 
						|
								        m_fieldTemplates[col->Id()] = field.m_Value;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Group multi-unit components together
							 | 
						|
								    m_components.clear();
							 | 
						|
								    m_fieldValues.clear();
							 | 
						|
								
							 | 
						|
								
							 | 
						|
								    // Iterate through each unique component
							 | 
						|
								    for( unsigned int ii=0; ii<aRefs.GetCount(); ii++ )
							 | 
						|
								    {
							 | 
						|
								        auto ref = aRefs.GetItem( ii );
							 | 
						|
								
							 | 
						|
								        bool found = false;
							 | 
						|
								
							 | 
						|
								        for( auto& cmp : m_components )
							 | 
						|
								        {
							 | 
						|
								            if( cmp->AddUnit( ref ) )
							 | 
						|
								            {
							 | 
						|
								                found = true;
							 | 
						|
								                break;
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        if( !found )
							 | 
						|
								        {
							 | 
						|
								            // Find the field:value map associated with this component
							 | 
						|
								            wxString refDes = ref.GetComp()->GetField( REFERENCE )->GetText();
							 | 
						|
								
							 | 
						|
								            bool dataFound = false;
							 | 
						|
								
							 | 
						|
								            BOM_FIELD_VALUES* values;
							 | 
						|
								
							 | 
						|
								            for( auto& data : m_fieldValues )
							 | 
						|
								            {
							 | 
						|
								                // Look for a match based on RefDes
							 | 
						|
								                if( data->GetReference().Cmp( refDes ) == 0 )
							 | 
						|
								                {
							 | 
						|
								                    dataFound = true;
							 | 
						|
								                    values = &*data;
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            if( !dataFound )
							 | 
						|
								            {
							 | 
						|
								                values = new BOM_FIELD_VALUES( refDes, &m_fieldTemplates );
							 | 
						|
								                m_fieldValues.push_back( std::unique_ptr<BOM_FIELD_VALUES>( values ) );
							 | 
						|
								            }
							 | 
						|
								
							 | 
						|
								            auto* newComponent = new BOM_TABLE_COMPONENT( nullptr, &ColumnList, values );
							 | 
						|
								            newComponent->AddUnit( ref );
							 | 
						|
								
							 | 
						|
								            m_components.push_back( std::unique_ptr<BOM_TABLE_COMPONENT>( newComponent ) );
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    SetBackupPoint();
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								void BOM_TABLE_MODEL::SetBackupPoint()
							 | 
						|
								{
							 | 
						|
								    // Mark backup locations for all values
							 | 
						|
								    for( auto& vals : m_fieldValues )
							 | 
						|
								    {
							 | 
						|
								        vals->SetBackupPoint();
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 *  Recalculate grouping of components and reload table
							 | 
						|
								 **/
							 | 
						|
								void BOM_TABLE_MODEL::ReloadTable()
							 | 
						|
								{
							 | 
						|
								    if( m_widget )
							 | 
						|
								    {
							 | 
						|
								        m_widget->Freeze();
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Alert the view that the model data has changed
							 | 
						|
								    Cleared();
							 | 
						|
								
							 | 
						|
								    Groups.clear();
							 | 
						|
								
							 | 
						|
								    for( auto& cmp : m_components )
							 | 
						|
								    {
							 | 
						|
								        bool grouped = false;
							 | 
						|
								
							 | 
						|
								        if( m_groupColumns )
							 | 
						|
								        {
							 | 
						|
								            for( auto& group : Groups )
							 | 
						|
								            {
							 | 
						|
								                if( group->AddComponent( &*cmp ) )
							 | 
						|
								                {
							 | 
						|
								                    grouped = true;
							 | 
						|
								                    break;
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        // No suitable group was found for this component
							 | 
						|
								        if( !grouped )
							 | 
						|
								        {
							 | 
						|
								            auto* newGroup = new BOM_TABLE_GROUP( &ColumnList );
							 | 
						|
								
							 | 
						|
								            newGroup->AddComponent( &*cmp );
							 | 
						|
								
							 | 
						|
								            Groups.push_back( std::unique_ptr<BOM_TABLE_GROUP>( newGroup ) );
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Update the display
							 | 
						|
								    if( m_widget )
							 | 
						|
								    {
							 | 
						|
								        //Cleared();
							 | 
						|
								        m_widget->AssociateModel( this );
							 | 
						|
								        m_widget->Thaw();
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return a string array of data from a given row
							 | 
						|
								 */
							 | 
						|
								wxArrayString BOM_TABLE_MODEL::GetRowData( unsigned int aRow, std::vector<BOM_COLUMN*> aColumns ) const
							 | 
						|
								{
							 | 
						|
								    wxArrayString row;
							 | 
						|
								
							 | 
						|
								    wxString data;
							 | 
						|
								
							 | 
						|
								    if( Groups.size() <= aRow )
							 | 
						|
								        return row;
							 | 
						|
								
							 | 
						|
								    auto const& group = Groups[aRow];
							 | 
						|
								
							 | 
						|
								    if ( !group )
							 | 
						|
								        return row;
							 | 
						|
								
							 | 
						|
								    for( auto const col : aColumns )
							 | 
						|
								    {
							 | 
						|
								        if( !col )
							 | 
						|
								        {
							 | 
						|
								            row.Add( wxEmptyString );
							 | 
						|
								            continue;
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        row.Add( group->GetFieldValue( col->Id() ) );
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return row;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Get the value of a particular item in the model
							 | 
						|
								 */
							 | 
						|
								void BOM_TABLE_MODEL::GetValue(
							 | 
						|
								        wxVariant& aVariant,
							 | 
						|
								        const wxDataViewItem& aItem,
							 | 
						|
								        unsigned int aFieldId ) const
							 | 
						|
								{
							 | 
						|
								    auto row = ItemToRow( aItem );
							 | 
						|
								
							 | 
						|
								    if( row )
							 | 
						|
								    {
							 | 
						|
								        aVariant = row->GetFieldValue( aFieldId );
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Set the value of a particular item in the model
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_MODEL::SetValue(
							 | 
						|
								        const wxVariant& aVariant,
							 | 
						|
								        const wxDataViewItem& aItem,
							 | 
						|
								        unsigned int aFieldId )
							 | 
						|
								{
							 | 
						|
								    if( !aItem.IsOk() || !m_widget )
							 | 
						|
								    {
							 | 
						|
								        return false;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Extract the value to be set
							 | 
						|
								    if( aVariant.GetType().Cmp( "string" ) == 0 )
							 | 
						|
								    {
							 | 
						|
								        wxString value = aVariant.GetString();
							 | 
						|
								
							 | 
						|
								        bool result = false;
							 | 
						|
								
							 | 
						|
								        wxDataViewItemArray selectedItems;
							 | 
						|
								        m_widget->GetSelections( selectedItems );
							 | 
						|
								
							 | 
						|
								        // Set the row value for all selected rows
							 | 
						|
								
							 | 
						|
								        for( auto item : selectedItems )
							 | 
						|
								        {
							 | 
						|
								            auto selectedRow = static_cast<BOM_TABLE_ROW*>( item.GetID() );
							 | 
						|
								
							 | 
						|
								            if( selectedRow )
							 | 
						|
								            {
							 | 
						|
								                result |= selectedRow->SetFieldValue( aFieldId, value, true );
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        if( m_widget )
							 | 
						|
								        {
							 | 
						|
								            m_widget->Update();
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        return result;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Default
							 | 
						|
								    return false;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Return the parent item for a given item in the model.
							 | 
						|
								 * If no parent is found (or the item is invalid) return an invalid item.
							 | 
						|
								 */
							 | 
						|
								wxDataViewItem BOM_TABLE_MODEL::GetParent( const wxDataViewItem& aItem ) const
							 | 
						|
								{
							 | 
						|
								    auto row = ItemToRow( aItem );
							 | 
						|
								    auto parent = row ? row->GetParent() : nullptr;
							 | 
						|
								
							 | 
						|
								    if( parent )
							 | 
						|
								    {
							 | 
						|
								        return RowToItem( parent );
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // Return an invalid item
							 | 
						|
								    return wxDataViewItem();
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Returns true if the supplied item has children
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_MODEL::IsContainer( const wxDataViewItem& aItem ) const
							 | 
						|
								{
							 | 
						|
								    auto row = ItemToRow( aItem );
							 | 
						|
								
							 | 
						|
								    if( row )
							 | 
						|
								    {
							 | 
						|
								        return row->HasChildren();
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return true;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Push all children of the supplied item into the list
							 | 
						|
								 * If the supplied item is invalid, push all the top-level items
							 | 
						|
								 */
							 | 
						|
								unsigned int BOM_TABLE_MODEL::GetChildren(
							 | 
						|
								        const wxDataViewItem& aItem,
							 | 
						|
								        wxDataViewItemArray& aChildren ) const
							 | 
						|
								{
							 | 
						|
								    auto row = aItem.IsOk() ? ItemToRow( aItem ) : nullptr;
							 | 
						|
								
							 | 
						|
								    // Valid row, return its children
							 | 
						|
								    if( row )
							 | 
						|
								    {
							 | 
						|
								        return row->GetChildren( aChildren );
							 | 
						|
								    }
							 | 
						|
								    else
							 | 
						|
								    {
							 | 
						|
								        for( auto& group : Groups )
							 | 
						|
								        {
							 | 
						|
								            aChildren.Add( RowToItem( &*group ) );
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        return aChildren.size();
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								bool BOM_TABLE_MODEL::GetAttr( const wxDataViewItem& aItem,
							 | 
						|
								                           unsigned int aFieldId,
							 | 
						|
								                           wxDataViewItemAttr& aAttr ) const
							 | 
						|
								{
							 | 
						|
								    auto row = aItem.IsOk() ? ItemToRow( aItem ) : nullptr;
							 | 
						|
								
							 | 
						|
								    if( row )
							 | 
						|
								    {
							 | 
						|
								        return row->GetAttr( aFieldId, aAttr );
							 | 
						|
								    }
							 | 
						|
								    else
							 | 
						|
								    {
							 | 
						|
								        return false;
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Custom comparison function for improved column sorting
							 | 
						|
								 * Alphanumeric sorting is not sufficient for correct ordering of some fields
							 | 
						|
								 * Some columns are sorted numerically, others with more complex rules.
							 | 
						|
								 */
							 | 
						|
								int BOM_TABLE_MODEL::Compare( const wxDataViewItem& aItem1,
							 | 
						|
								                          const wxDataViewItem& aItem2,
							 | 
						|
								                          unsigned int aColumnId,
							 | 
						|
								                          bool aAscending ) const
							 | 
						|
								{
							 | 
						|
								    if( !aItem1.IsOk() || !aItem2.IsOk() )
							 | 
						|
								        return 0;
							 | 
						|
								
							 | 
						|
								    int result = 0;
							 | 
						|
								
							 | 
						|
								    auto row1 = ItemToRow( aItem1 );
							 | 
						|
								    auto row2 = ItemToRow( aItem2 );
							 | 
						|
								
							 | 
						|
								    if( !row1 || !row2 )
							 | 
						|
								        return 0;
							 | 
						|
								
							 | 
						|
								    if( row1->GetParent() != row2->GetParent() )
							 | 
						|
								        return 0;
							 | 
						|
								
							 | 
						|
								    wxString strVal1 = row1->GetFieldValue( aColumnId );
							 | 
						|
								    wxString strVal2 = row2->GetFieldValue( aColumnId );
							 | 
						|
								
							 | 
						|
								    long numVal1;
							 | 
						|
								    long numVal2;
							 | 
						|
								
							 | 
						|
								    switch( aColumnId )
							 | 
						|
								    {
							 | 
						|
								    // Reference column sorted by reference val
							 | 
						|
								    case BOM_COL_ID_REFERENCE:
							 | 
						|
								        result = BOM_TABLE_GROUP::SortReferences( strVal1, strVal2 );
							 | 
						|
								        break;
							 | 
						|
								    case BOM_COL_ID_VALUE:
							 | 
						|
								        result = BOM_TABLE_GROUP::SortValues( strVal1, strVal2 );
							 | 
						|
								        break;
							 | 
						|
								    // These columns are sorted numerically
							 | 
						|
								    case BOM_COL_ID_QUANTITY:
							 | 
						|
								        if( strVal1.ToLong( &numVal1 ) && strVal2.ToLong( &numVal2 ) )
							 | 
						|
								        {
							 | 
						|
								            result = numVal1 - numVal2;
							 | 
						|
								        }
							 | 
						|
								        else
							 | 
						|
								        {
							 | 
						|
								            result = strVal1.Cmp( strVal2 );
							 | 
						|
								        }
							 | 
						|
								        break;
							 | 
						|
								    default:
							 | 
						|
								        // Default comparison (no special case)
							 | 
						|
								        result = strVal1.Cmp( strVal2 );
							 | 
						|
								        break;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // If initial sorting failed, sort secondly by reference
							 | 
						|
								    if( result == 0 && aColumnId != BOM_COL_ID_REFERENCE )
							 | 
						|
								    {
							 | 
						|
								        result = BOM_TABLE_GROUP::SortReferences(
							 | 
						|
								                row1->GetFieldValue( BOM_COL_ID_REFERENCE ),
							 | 
						|
								                row2->GetFieldValue( BOM_COL_ID_REFERENCE ) );
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    // If sorting still failed, sort thirdly by value
							 | 
						|
								    if( result == 0 && aColumnId != BOM_COL_ID_VALUE )
							 | 
						|
								    {
							 | 
						|
								        result = BOM_TABLE_GROUP::SortValues(
							 | 
						|
								                row1->GetFieldValue( BOM_COL_ID_VALUE ),
							 | 
						|
								                row2->GetFieldValue( BOM_COL_ID_VALUE ) );
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    if( !aAscending )
							 | 
						|
								    {
							 | 
						|
								        result *= -1;
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return result;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Revert all component data back to the original values.
							 | 
						|
								 * The table view is updated accordingly
							 | 
						|
								 */
							 | 
						|
								void BOM_TABLE_MODEL::RevertFieldChanges()
							 | 
						|
								{
							 | 
						|
								    for( auto& group : Groups )
							 | 
						|
								    {
							 | 
						|
								        if( !group )
							 | 
						|
								            continue;
							 | 
						|
								
							 | 
						|
								        bool changed = false;
							 | 
						|
								
							 | 
						|
								        for( auto& component : group->Components )
							 | 
						|
								        {
							 | 
						|
								            if( !component )
							 | 
						|
								                continue;
							 | 
						|
								
							 | 
						|
								            if( component->HasChanged() )
							 | 
						|
								            {
							 | 
						|
								                component->RevertFieldChanges();
							 | 
						|
								                ItemChanged( RowToItem( &*component ) );
							 | 
						|
								                changed = true;
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								
							 | 
						|
								        // Update the group if any components changed
							 | 
						|
								        if( changed )
							 | 
						|
								        {
							 | 
						|
								            ItemChanged( RowToItem( &*group ) );
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Apply all outstanding field changes.
							 | 
						|
								 * This is performed only when the window is closed
							 | 
						|
								 */
							 | 
						|
								void BOM_TABLE_MODEL::ApplyFieldChanges()
							 | 
						|
								{
							 | 
						|
								    for( auto& group : Groups )
							 | 
						|
								    {
							 | 
						|
								        if( !group )
							 | 
						|
								            continue;
							 | 
						|
								
							 | 
						|
								        for( auto& component : group->Components )
							 | 
						|
								        {
							 | 
						|
								            if( !component )
							 | 
						|
								                continue;
							 | 
						|
								
							 | 
						|
								            if( component->HasChanged() )
							 | 
						|
								            {
							 | 
						|
								                component->ApplyFieldChanges();
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Tests if any component values in the table have been altered
							 | 
						|
								 */
							 | 
						|
								bool BOM_TABLE_MODEL::HaveFieldsChanged() const
							 | 
						|
								{
							 | 
						|
								    for( auto const& group : Groups )
							 | 
						|
								    {
							 | 
						|
								        if( !group )
							 | 
						|
								            continue;
							 | 
						|
								
							 | 
						|
								        for( auto const& cmp : group->Components )
							 | 
						|
								        {
							 | 
						|
								            if( !cmp )
							 | 
						|
								                continue;
							 | 
						|
								
							 | 
						|
								            if( cmp->HasChanged() )
							 | 
						|
								            {
							 | 
						|
								                return true;
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return false;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Returns a list of only those components that have been changed
							 | 
						|
								 */
							 | 
						|
								std::vector<SCH_REFERENCE> BOM_TABLE_MODEL::GetChangedComponents()
							 | 
						|
								{
							 | 
						|
								    std::vector<SCH_REFERENCE> components;
							 | 
						|
								
							 | 
						|
								    for( auto& group : Groups )
							 | 
						|
								    {
							 | 
						|
								        if( !group )
							 | 
						|
								            continue;
							 | 
						|
								
							 | 
						|
								        for( auto& component : group->Components )
							 | 
						|
								        {
							 | 
						|
								            if( !component )
							 | 
						|
								                continue;
							 | 
						|
								
							 | 
						|
								            if( component->HasChanged() )
							 | 
						|
								            {
							 | 
						|
								                for( auto& unit : component->Units )
							 | 
						|
								                {
							 | 
						|
								                    components.push_back( unit );
							 | 
						|
								                }
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return components;
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								/**
							 | 
						|
								 * Returns a count of the components that have been changed
							 | 
						|
								 */
							 | 
						|
								unsigned int BOM_TABLE_MODEL::CountChangedComponents()
							 | 
						|
								{
							 | 
						|
								    unsigned int count = 0;
							 | 
						|
								
							 | 
						|
								    for( auto& group : Groups )
							 | 
						|
								    {
							 | 
						|
								        if( !group )
							 | 
						|
								            continue;
							 | 
						|
								
							 | 
						|
								        for( auto& component : group->Components )
							 | 
						|
								        {
							 | 
						|
								            if( component && component->HasChanged() )
							 | 
						|
								            {
							 | 
						|
								                count++;
							 | 
						|
								            }
							 | 
						|
								        }
							 | 
						|
								    }
							 | 
						|
								
							 | 
						|
								    return count;
							 | 
						|
								}
							 |