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.
		
		
		
		
		
			
		
			
				
					
					
						
							540 lines
						
					
					
						
							17 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							540 lines
						
					
					
						
							17 KiB
						
					
					
				| /* | |
|  * This program source code file is part of KiCad, a free EDA CAD application. | |
|  * | |
|  * Copyright (C) 2018 CERN | |
|  * Copyright The KiCad Developers, see AUTHORS.txt for contributors. | |
|  * @author Jon Evans <jon@craftyjon.com> | |
|  * | |
|  * 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 3 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, see <http://www.gnu.org/licenses/>. | |
|  */ | |
| 
 | |
| #include <widgets/wx_grid.h> | |
| #include <widgets/std_bitmap_button.h> | |
| #include <confirm.h> | |
| #include <sch_edit_frame.h> | |
| #include <schematic.h> | |
| #include <dialogs/panel_setup_buses.h> | |
| #include "grid_tricks.h" | |
| #include <wx/clipbrd.h> | |
|  | |
| PANEL_SETUP_BUSES::PANEL_SETUP_BUSES( wxWindow* aWindow, SCH_EDIT_FRAME* aFrame ) : | |
|         PANEL_SETUP_BUSES_BASE( aWindow ), | |
|         m_frame( aFrame ), | |
|         m_lastAlias( 0 ), | |
|         m_membersGridDirty( false ), | |
|         m_errorGrid( nullptr ), | |
|         m_errorRow( -1 ) | |
| { | |
|     m_membersLabelTemplate = m_membersLabel->GetLabel(); | |
| 
 | |
|     m_addAlias->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) ); | |
|     m_deleteAlias->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) ); | |
|     m_addMember->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) ); | |
|     m_removeMember->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) ); | |
| 
 | |
|     m_source->SetFont( KIUI::GetInfoFont( aWindow ) ); | |
| 
 | |
|     m_aliasesGrid->OverrideMinSize( 0.6, 0.3 ); | |
|     m_membersGrid->OverrideMinSize( 0.6, 0.3 ); | |
|     m_aliasesGrid->SetSelectionMode( wxGrid::wxGridSelectRows ); | |
|     m_membersGrid->SetSelectionMode( wxGrid::wxGridSelectRows ); | |
| 
 | |
|     m_aliasesGrid->PushEventHandler( new GRID_TRICKS( m_aliasesGrid, | |
|                                                       [this]( wxCommandEvent& aEvent ) | |
|                                                       { | |
|                                                           OnAddAlias( aEvent ); | |
|                                                       } ) ); | |
| 
 | |
|     m_membersGrid->PushEventHandler( new GRID_TRICKS( m_membersGrid, | |
|                                                       [this]( wxCommandEvent& aEvent ) | |
|                                                       { | |
|                                                           OnAddMember( aEvent ); | |
|                                                       } ) ); | |
| 
 | |
|     m_aliasesGrid->SetUseNativeColLabels(); | |
|     m_membersGrid->SetUseNativeColLabels(); | |
| 
 | |
|     // wxFormBuilder doesn't include this event... | |
|     m_aliasesGrid->Connect( wxEVT_GRID_CELL_CHANGING, | |
|                             wxGridEventHandler( PANEL_SETUP_BUSES::OnAliasesGridCellChanging ), | |
|                             nullptr, this ); | |
|     m_membersGrid->Connect( wxEVT_GRID_CELL_CHANGING, | |
|                             wxGridEventHandler( PANEL_SETUP_BUSES::OnMemberGridCellChanging ), | |
|                             nullptr, this ); | |
| 
 | |
|     Layout(); | |
| } | |
| 
 | |
| 
 | |
| PANEL_SETUP_BUSES::~PANEL_SETUP_BUSES() | |
| { | |
|     // Delete the GRID_TRICKS. | |
|     m_aliasesGrid->PopEventHandler( true ); | |
|     m_membersGrid->PopEventHandler( true ); | |
| 
 | |
|     m_aliasesGrid->Disconnect( wxEVT_GRID_CELL_CHANGING, | |
|                                wxGridEventHandler( PANEL_SETUP_BUSES::OnAliasesGridCellChanging ), | |
|                                nullptr, this ); | |
|     m_membersGrid->Disconnect( wxEVT_GRID_CELL_CHANGING, | |
|                                wxGridEventHandler( PANEL_SETUP_BUSES::OnMemberGridCellChanging ), | |
|                                nullptr, this ); | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::loadAliases( const SCHEMATIC& aSchematic ) | |
| { | |
|     auto contains = | |
|             [&]( const std::shared_ptr<BUS_ALIAS>& alias ) -> bool | |
|             { | |
|                 wxString              aName = alias->GetName(); | |
|                 std::vector<wxString> aMembers = alias->Members(); | |
| 
 | |
|                 std::sort( aMembers.begin(), aMembers.end() ); | |
| 
 | |
|                 for( const std::shared_ptr<BUS_ALIAS>& candidate : m_aliases ) | |
|                 { | |
|                     wxString              bName = candidate->GetName(); | |
|                     std::vector<wxString> bMembers = candidate->Members(); | |
| 
 | |
|                     std::sort( bMembers.begin(), bMembers.end() ); | |
| 
 | |
|                     if( aName == bName && aMembers == bMembers ) | |
|                         return true; | |
|                 } | |
| 
 | |
|                 return false; | |
|             }; | |
| 
 | |
|     SCH_SCREENS screens( aSchematic.Root() ); | |
| 
 | |
|     // collect aliases from each open sheet | |
|     for( SCH_SCREEN* screen = screens.GetFirst(); screen != nullptr; screen = screens.GetNext() ) | |
|     { | |
|         for( const std::shared_ptr<BUS_ALIAS>& alias : screen->GetBusAliases() ) | |
|         { | |
|             if( !contains( alias ) ) | |
|                 m_aliases.push_back( alias->Clone() ); | |
|         } | |
|     } | |
| 
 | |
|     int ii = 0; | |
| 
 | |
|     m_aliasesGrid->ClearRows(); | |
|     m_aliasesGrid->AppendRows( m_aliases.size() ); | |
| 
 | |
|     for( const std::shared_ptr<BUS_ALIAS>& alias : m_aliases ) | |
|         m_aliasesGrid->SetCellValue( ii++, 0, alias->GetName() ); | |
| 
 | |
|     m_membersBook->SetSelection( 1 ); | |
| } | |
| 
 | |
| 
 | |
| bool PANEL_SETUP_BUSES::TransferDataToWindow() | |
| { | |
|     loadAliases( m_frame->Schematic() ); | |
|     return true; | |
| } | |
| 
 | |
| 
 | |
| bool PANEL_SETUP_BUSES::TransferDataFromWindow() | |
| { | |
|     if( !m_aliasesGrid->CommitPendingChanges() || !m_membersGrid->CommitPendingChanges() ) | |
|         return false; | |
| 
 | |
|     // Copy names back just in case they didn't get caught on the GridCellChanging event | |
|     for( int ii = 0; ii < m_aliasesGrid->GetNumberRows(); ++ii ) | |
|         m_aliases[ii]->SetName( m_aliasesGrid->GetCellValue( ii, 0 ) ); | |
| 
 | |
|     // Associate the respective members with the last alias that is active. | |
|     updateAliasMembers( m_lastAlias ); | |
| 
 | |
|     SCH_SCREENS screens( m_frame->Schematic().Root() ); | |
| 
 | |
|     for( SCH_SCREEN* screen = screens.GetFirst(); screen != nullptr; screen = screens.GetNext() ) | |
|         screen->ClearBusAliases(); | |
| 
 | |
|     for( const std::shared_ptr<BUS_ALIAS>& alias : m_aliases ) | |
|         alias->GetParent()->AddBusAlias( alias ); | |
| 
 | |
|     return true; | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::OnAddAlias( wxCommandEvent& aEvent ) | |
| { | |
|     if( !m_aliasesGrid->CommitPendingChanges() || !m_membersGrid->CommitPendingChanges() ) | |
|         return; | |
| 
 | |
|     // New aliases get stored on the currently visible sheet | |
|     m_aliases.push_back( std::make_shared<BUS_ALIAS>( m_frame->GetScreen() ) ); | |
| 
 | |
|     int row = m_aliasesGrid->GetNumberRows(); | |
| 
 | |
|     // Associate the respective members with the previous alias. This ensures that the association | |
|     // starts correctly when adding more than one row. | |
|     // But in order to avoid overwriting the members of the last (row - 1) alias with | |
|     // those of the selected alias (since the user may choose a different alias), | |
|     // the alias member update here should only happen if the current alias (m_lastAlias) is the last one (row - 1). | |
|     if( ( row > 0 ) && ( m_lastAlias == ( row - 1 ) ) ) | |
|         updateAliasMembers( row - 1 ); | |
| 
 | |
|     m_aliasesGrid->AppendRows(); | |
| 
 | |
|     m_aliasesGrid->MakeCellVisible( row, 0 ); | |
|     m_aliasesGrid->SetGridCursor( row, 0 ); | |
| 
 | |
|     m_aliasesGrid->EnableCellEditControl( true ); | |
|     m_aliasesGrid->ShowCellEditControl(); | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::OnDeleteAlias( wxCommandEvent& aEvent ) | |
| { | |
|     if( !m_aliasesGrid->CommitPendingChanges() || !m_membersGrid->CommitPendingChanges() ) | |
|         return; | |
| 
 | |
|     int curRow = m_aliasesGrid->GetGridCursorRow(); | |
| 
 | |
|     if( curRow < 0 ) | |
|         return; | |
| 
 | |
|     // Clear the members grid first so we don't try to write it back to a deleted alias | |
|     m_membersGrid->ClearRows(); | |
|     m_lastAlias = -1; | |
|     m_lastAliasName = wxEmptyString; | |
| 
 | |
|     m_aliases.erase( m_aliases.begin() + curRow ); | |
| 
 | |
|     m_aliasesGrid->DeleteRows( curRow, 1 ); | |
| 
 | |
|     if( m_aliasesGrid->GetNumberRows() > 0 ) | |
|     { | |
|         m_aliasesGrid->MakeCellVisible( std::max( 0, curRow-1 ), 0 ); | |
|         m_aliasesGrid->SelectRow( std::max( 0, curRow-1 ) ); | |
|     } | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::OnAddMember( wxCommandEvent& aEvent ) | |
| { | |
|     if( !m_membersGrid->CommitPendingChanges() ) | |
|         return; | |
| 
 | |
|     int row = m_membersGrid->GetNumberRows(); | |
|     m_membersGrid->AppendRows(); | |
| 
 | |
|     m_membersGrid->MakeCellVisible( row, 0 ); | |
|     m_membersGrid->SetGridCursor( row, 0 ); | |
| 
 | |
|     /* | |
|      * Check if the clipboard contains text data. | |
|      * | |
|      * - If `clipboardHasText` is true, select the specified row in the members grid to allow | |
|      *   our custom context menu to paste the clipboard . | |
|      * - Otherwise, enable and display the cell edit control, allowing the user to manually edit | |
|      *   the cell. | |
|      */ | |
|     bool clipboardHasText = false; | |
| 
 | |
|     if( wxTheClipboard->Open() ) | |
|     { | |
|         if( wxTheClipboard->IsSupported( wxDF_TEXT ) | |
|             || wxTheClipboard->IsSupported( wxDF_UNICODETEXT ) ) | |
|         { | |
|             clipboardHasText = true; | |
|         } | |
| 
 | |
|         wxTheClipboard->Close(); | |
|     } | |
| 
 | |
|     if( clipboardHasText ) | |
|     { | |
|         m_membersGrid->SelectRow( row ); | |
|     } | |
|     else | |
|     { | |
|         m_membersGrid->EnableCellEditControl( true ); | |
|         m_membersGrid->ShowCellEditControl(); | |
|     } | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::OnRemoveMember( wxCommandEvent& aEvent ) | |
| { | |
|     if( !m_membersGrid->CommitPendingChanges() ) | |
|         return; | |
| 
 | |
|     int curRow = m_membersGrid->GetGridCursorRow(); | |
| 
 | |
|     if( curRow < 0 ) | |
|         return; | |
| 
 | |
|     m_membersGrid->DeleteRows( curRow, 1 ); | |
| 
 | |
|     // Update the member list of the current bus alias from the members grid | |
|     const std::shared_ptr<BUS_ALIAS>& alias = m_aliases[ m_lastAlias ]; | |
|     alias->Members().clear(); | |
| 
 | |
|     for( int ii = 0; ii < m_membersGrid->GetNumberRows(); ++ii ) | |
|         alias->Members().push_back( m_membersGrid->GetCellValue( ii, 0 ) ); | |
| 
 | |
|     if( m_membersGrid->GetNumberRows() > 0 ) | |
|     { | |
|         m_membersGrid->MakeCellVisible( std::max( 0, curRow-1 ), 0 ); | |
|         m_membersGrid->SelectRow( std::max( 0, curRow-1 ) ); | |
|     } | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::OnAliasesGridCellChanging( wxGridEvent& event ) | |
| { | |
|     int row = event.GetRow(); | |
| 
 | |
|     if( row >= 0 ) | |
|     { | |
|         wxString name = event.GetString(); | |
| 
 | |
|         for( int ii = 0; ii < m_aliasesGrid->GetNumberRows(); ++ii ) | |
|         { | |
|             if( ii == event.GetRow() ) | |
|                 continue; | |
| 
 | |
|             if( name == m_aliasesGrid->GetCellValue( ii, 0 ) | |
|                     && m_aliases[ row ]->GetParent() == m_aliases[ ii ]->GetParent() ) | |
|             { | |
|                 m_errorMsg = wxString::Format( _( "Alias name '%s' already in use." ), name ); | |
|                 m_errorGrid = m_aliasesGrid; | |
|                 m_errorRow = row; | |
| 
 | |
|                 event.Veto(); | |
|                 return; | |
|             } | |
|         } | |
| 
 | |
|         m_aliases[ row ]->SetName( name ); | |
|     } | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::OnMemberGridCellChanging( wxGridEvent& event ) | |
| { | |
|     int row = event.GetRow(); | |
| 
 | |
|     if( row >= 0 ) | |
|     { | |
|         wxString name = event.GetString(); | |
| 
 | |
|         if( name.IsEmpty() ) | |
|         { | |
|             m_errorMsg = _( "Member net/alias name cannot be empty." ); | |
|             m_errorGrid = m_membersGrid; | |
|             m_errorRow = event.GetRow(); | |
| 
 | |
|             event.Veto(); | |
|             return; | |
|         } | |
| 
 | |
|         const std::shared_ptr<BUS_ALIAS>& alias = m_aliases[ m_lastAlias ]; | |
| 
 | |
|         alias->Members().clear(); | |
| 
 | |
|         for( int ii = 0; ii < m_membersGrid->GetNumberRows(); ++ii ) | |
|         { | |
|             if( ii == row ) | |
|             { | |
|                 // Parse a space-separated list and add each one | |
|                 wxStringTokenizer tok( name, " " ); | |
| 
 | |
|                 if( tok.CountTokens() > 1 ) | |
|                 { | |
|                     m_membersGridDirty = true; | |
|                     Bind( wxEVT_IDLE, &PANEL_SETUP_BUSES::reloadMembersGridOnIdle, this ); | |
|                 } | |
| 
 | |
|                 while( tok.HasMoreTokens() ) | |
|                     alias->Members().push_back( tok.GetNextToken() ); | |
|             } | |
|             else | |
|             { | |
|                 alias->Members().push_back( m_membersGrid->GetCellValue( ii, 0 ) ); | |
|             } | |
|         } | |
|     } | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::doReloadMembersGrid() | |
| { | |
|     if( m_lastAlias >= 0 && m_lastAlias < m_aliasesGrid->GetNumberRows() ) | |
|     { | |
|         const std::shared_ptr<BUS_ALIAS>& alias = m_aliases[ m_lastAlias ]; | |
|         wxString                          source; | |
|         wxString                          membersLabel; | |
| 
 | |
|         if( alias->GetParent() ) | |
|         { | |
|             wxFileName sheet_name( alias->GetParent()->GetFileName() ); | |
|             source.Printf( wxS( "(" ) + sheet_name.GetFullName() + wxS( ")" ) ); | |
|         } | |
| 
 | |
|         membersLabel.Printf( m_membersLabelTemplate, m_lastAliasName ); | |
| 
 | |
|         m_source->SetLabel( source ); | |
|         m_membersLabel->SetLabel( membersLabel ); | |
| 
 | |
|         m_membersGrid->ClearRows(); | |
|         m_membersGrid->AppendRows( alias->Members().size() ); | |
| 
 | |
|         int ii = 0; | |
| 
 | |
|         for( const wxString& member : alias->Members() ) | |
|             m_membersGrid->SetCellValue( ii++, 0, member ); | |
|     } | |
| 
 | |
|     m_membersGridDirty = false; | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::reloadMembersGridOnIdle( wxIdleEvent& aEvent ) | |
| { | |
|     if( m_membersGridDirty ) | |
|         doReloadMembersGrid(); | |
| 
 | |
|     Unbind( wxEVT_IDLE, &PANEL_SETUP_BUSES::reloadMembersGridOnIdle, this ); | |
| } | |
| 
 | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::OnSizeGrid( wxSizeEvent& event ) | |
| { | |
|     auto setColSize = | |
|             []( WX_GRID* grid ) | |
|             { | |
|                 int colSize = std::max( grid->GetClientSize().x, grid->GetVisibleWidth( 0 ) ); | |
| 
 | |
|                 if( grid->GetColSize( 0 ) != colSize ) | |
|                     grid->SetColSize( 0, colSize ); | |
|             }; | |
| 
 | |
|     setColSize( m_aliasesGrid ); | |
|     setColSize( m_membersGrid ); | |
| 
 | |
|     // Always propagate for a grid repaint (needed if the height changes, as well as width) | |
|     event.Skip(); | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::OnUpdateUI( wxUpdateUIEvent& event ) | |
| { | |
|     // Handle a grid error.  This is delayed to OnUpdateUI so that we can change focus | |
|     // even when the original validation was triggered from a killFocus event. | |
|     if( !m_errorMsg.IsEmpty() ) | |
|     { | |
|         // We will re-enter this routine when the error dialog is displayed, so make | |
|         // sure we don't keep putting up more dialogs. | |
|         wxString errorMsg = m_errorMsg; | |
|         m_errorMsg = wxEmptyString; | |
| 
 | |
|         wxWindow* topLevelParent = wxGetTopLevelParent( this ); | |
| 
 | |
|         DisplayErrorMessage( topLevelParent, errorMsg ); | |
| 
 | |
|         m_errorGrid->SetFocus(); | |
|         m_errorGrid->MakeCellVisible( m_errorRow, 0 ); | |
|         m_errorGrid->SetGridCursor( m_errorRow, 0 ); | |
| 
 | |
|         m_errorGrid->EnableCellEditControl( true ); | |
|         m_errorGrid->ShowCellEditControl(); | |
| 
 | |
|         return; | |
|     } | |
| 
 | |
|     if( !m_membersGrid->IsCellEditControlShown() ) | |
|     { | |
|         int      row = -1; | |
|         wxString aliasName; | |
| 
 | |
|         if( m_aliasesGrid->IsCellEditControlShown() ) | |
|         { | |
|             row = m_aliasesGrid->GetGridCursorRow(); | |
|             wxGridCellEditor* cellEditor = m_aliasesGrid->GetCellEditor( row, 0 ); | |
| 
 | |
|             if( wxTextEntry* txt = dynamic_cast<wxTextEntry*>( cellEditor->GetControl() ) ) | |
|                 aliasName = txt->GetValue(); | |
| 
 | |
|             cellEditor->DecRef(); | |
|         } | |
|         else if( m_aliasesGrid->GetGridCursorRow() >= 0 ) | |
|         { | |
|             row = m_aliasesGrid->GetGridCursorRow(); | |
|             aliasName = m_aliasesGrid->GetCellValue( row, 0 ); | |
|         } | |
|         else if( m_lastAlias >= 0 && m_lastAlias < m_aliasesGrid->GetNumberRows() ) | |
|         { | |
|             row = m_lastAlias; | |
|             aliasName = m_lastAliasName; | |
|         } | |
| 
 | |
|         if( row < 0 ) | |
|         { | |
|             m_membersBook->SetSelection( 1 ); | |
|         } | |
|         else if( row != m_lastAlias || aliasName != m_lastAliasName ) | |
|         { | |
|             m_lastAlias = row; | |
|             m_lastAliasName = aliasName; | |
| 
 | |
|             m_membersBook->SetSelection( 0 ); | |
|             m_membersBook->GetPage( 0 )->Layout(); | |
| 
 | |
|             const std::shared_ptr<BUS_ALIAS>& alias = m_aliases[ row ]; | |
|             alias->SetName( aliasName ); | |
| 
 | |
|             m_membersGridDirty = true; | |
|             Bind( wxEVT_IDLE, &PANEL_SETUP_BUSES::reloadMembersGridOnIdle, this ); | |
|         } | |
|     } | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::ImportSettingsFrom( const SCHEMATIC& aOtherSchematic ) | |
| { | |
|     loadAliases( aOtherSchematic ); | |
| 
 | |
|     // New aliases get stored on the currently visible sheet | |
|     for( const std::shared_ptr<BUS_ALIAS>& alias : m_aliases ) | |
|         alias->SetParent( m_frame->GetScreen() ); | |
| } | |
| 
 | |
| 
 | |
| void PANEL_SETUP_BUSES::updateAliasMembers( int aAliasIndex ) | |
| { | |
|     if( !m_aliases.empty() && m_membersGrid->GetNumberRows() > 0 && aAliasIndex >= 0 | |
|         && aAliasIndex < (int)m_aliases.size() ) | |
|     { | |
|         const std::shared_ptr<BUS_ALIAS>& alias = m_aliases[aAliasIndex]; | |
| 
 | |
|         alias->Members().clear(); | |
| 
 | |
|         for( int ii = 0; ii < m_membersGrid->GetNumberRows(); ++ii ) | |
|         { | |
|             wxString memberValue = m_membersGrid->GetCellValue( ii, 0 ); | |
| 
 | |
|             if( !memberValue.empty() ) | |
|             { | |
|                 alias->Members().push_back( memberValue ); | |
|             } | |
|         } | |
|     } | |
| }
 |