/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 Jon Evans * Copyright (C) 2021 KiCad Developers, see AUTHORS.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 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include NET_GRID_TABLE::NET_GRID_TABLE( PCB_BASE_FRAME* aFrame, wxColor aBackgroundColor ) : wxGridTableBase(), m_frame( aFrame ) { m_defaultAttr = new wxGridCellAttr; m_defaultAttr->SetBackgroundColour( aBackgroundColor ); m_labelAttr = new wxGridCellAttr; m_labelAttr->SetRenderer( new GRID_CELL_ESCAPED_TEXT_RENDERER ); m_labelAttr->SetBackgroundColour( aBackgroundColor ); } NET_GRID_TABLE::~NET_GRID_TABLE() { m_defaultAttr->DecRef(); m_labelAttr->DecRef(); } wxGridCellAttr* NET_GRID_TABLE::GetAttr( int aRow, int aCol, wxGridCellAttr::wxAttrKind ) { switch( aCol ) { case COL_COLOR: m_defaultAttr->IncRef(); return m_defaultAttr; case COL_VISIBILITY: m_defaultAttr->IncRef(); return m_defaultAttr; case COL_LABEL: m_labelAttr->IncRef(); return m_labelAttr; default: wxFAIL; return nullptr; } } wxString NET_GRID_TABLE::GetValue( int aRow, int aCol ) { wxASSERT( static_cast( aRow ) < m_nets.size() ); switch( aCol ) { case COL_COLOR: return m_nets[aRow].color.ToWxString( wxC2S_CSS_SYNTAX ); case COL_VISIBILITY: return m_nets[aRow].visible ? "1" : "0"; case COL_LABEL: return m_nets[aRow].name; default: return wxEmptyString; } } void NET_GRID_TABLE::SetValue( int aRow, int aCol, const wxString& aValue ) { wxASSERT( static_cast( aRow ) < m_nets.size() ); NET_GRID_ENTRY& net = m_nets[aRow]; switch( aCol ) { case COL_COLOR: net.color.SetFromWxString( aValue ); updateNetColor( net ); break; case COL_VISIBILITY: net.visible = ( aValue != "0" ); updateNetVisibility( net ); break; case COL_LABEL: net.name = aValue; break; default: break; } } wxString NET_GRID_TABLE::GetTypeName( int aRow, int aCol ) { switch( aCol ) { case COL_COLOR: return wxT( "COLOR4D" ); case COL_VISIBILITY: return wxGRID_VALUE_BOOL; case COL_LABEL: return wxGRID_VALUE_STRING; default: return wxGRID_VALUE_STRING; } } bool NET_GRID_TABLE::GetValueAsBool( int aRow, int aCol ) { wxASSERT( static_cast( aRow ) < m_nets.size() ); wxASSERT( aCol == COL_VISIBILITY ); return m_nets[aRow].visible; } void NET_GRID_TABLE::SetValueAsBool( int aRow, int aCol, bool aValue ) { wxASSERT( static_cast( aRow ) < m_nets.size() ); wxASSERT( aCol == COL_VISIBILITY ); m_nets[aRow].visible = aValue; updateNetVisibility( m_nets[aRow] ); } void* NET_GRID_TABLE::GetValueAsCustom( int aRow, int aCol, const wxString& aTypeName ) { wxASSERT( aCol == COL_COLOR ); wxASSERT( aTypeName == wxT( "COLOR4D" ) ); wxASSERT( static_cast( aRow ) < m_nets.size() ); return ColorToVoid( m_nets[aRow].color ); } void NET_GRID_TABLE::SetValueAsCustom( int aRow, int aCol, const wxString& aTypeName, void* aValue ) { wxASSERT( aCol == COL_COLOR ); wxASSERT( aTypeName == wxT( "COLOR4D" ) ); wxASSERT( static_cast( aRow ) < m_nets.size() ); m_nets[aRow].color = VoidToColor( aValue ); updateNetColor( m_nets[aRow] ); if( GetView() ) { wxGridTableMessage msg( this, wxGRIDTABLE_REQUEST_VIEW_GET_VALUES ); GetView()->ProcessTableMessage( msg ); } } NET_GRID_ENTRY& NET_GRID_TABLE::GetEntry( int aRow ) { wxASSERT( static_cast( aRow ) < m_nets.size() ); return m_nets[aRow]; } int NET_GRID_TABLE::GetRowByNetcode( int aCode ) const { auto it = std::find_if( m_nets.cbegin(), m_nets.cend(), [aCode]( const NET_GRID_ENTRY& aEntry ) { return aEntry.code == aCode; } ); if( it == m_nets.cend() ) return -1; return std::distance( m_nets.cbegin(), it ); } void NET_GRID_TABLE::Rebuild() { BOARD* board = m_frame->GetBoard(); const NETNAMES_MAP& nets = board->GetNetInfo().NetsByName(); KIGFX::PCB_RENDER_SETTINGS* rs = static_cast( m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings() ); std::set& hiddenNets = rs->GetHiddenNets(); std::map& netColors = rs->GetNetColorMap(); int deleted = m_nets.size(); m_nets.clear(); if( GetView() ) { wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, deleted ); GetView()->ProcessTableMessage( msg ); } for( const std::pair& pair : nets ) { int netCode = pair.second->GetNetCode(); if( netCode > 0 && !pair.first.StartsWith( "unconnected-(" ) ) { COLOR4D color = netColors.count( netCode ) ? netColors.at( netCode ) : COLOR4D::UNSPECIFIED; bool visible = hiddenNets.count( netCode ) == 0; m_nets.emplace_back( NET_GRID_ENTRY( netCode, pair.first, color, visible ) ); } } // TODO(JE) move to ::Compare so we can re-sort easily std::sort( m_nets.begin(), m_nets.end(), []( const NET_GRID_ENTRY& a, const NET_GRID_ENTRY& b ) { return a.name < b.name; } ); if( GetView() ) { wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_nets.size() ); GetView()->ProcessTableMessage( msg ); } } void NET_GRID_TABLE::ShowAllNets() { for( NET_GRID_ENTRY& net : m_nets ) { net.visible = true; updateNetVisibility( net ); } if( GetView() ) GetView()->ForceRefresh(); } void NET_GRID_TABLE::HideOtherNets( const NET_GRID_ENTRY& aNet ) { for( NET_GRID_ENTRY& net : m_nets ) { net.visible = ( net.code == aNet.code ); updateNetVisibility( net ); } if( GetView() ) GetView()->ForceRefresh(); } void NET_GRID_TABLE::updateNetVisibility( const NET_GRID_ENTRY& aNet ) { const TOOL_ACTION& action = aNet.visible ? PCB_ACTIONS::showNet : PCB_ACTIONS::hideNet; m_frame->GetToolManager()->RunAction( action, true, static_cast( aNet.code ) ); } void NET_GRID_TABLE::updateNetColor( const NET_GRID_ENTRY& aNet ) { KIGFX::PCB_RENDER_SETTINGS* rs = static_cast( m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings() ); std::map& netColors = rs->GetNetColorMap(); if( aNet.color != COLOR4D::UNSPECIFIED ) netColors[aNet.code] = aNet.color; else netColors.erase( aNet.code ); m_frame->GetCanvas()->GetView()->UpdateAllLayersColor(); m_frame->GetCanvas()->RedrawRatsnest(); m_frame->GetCanvas()->Refresh(); } /// Template for object appearance settings const APPEARANCE_CONTROLS::APPEARANCE_SETTING APPEARANCE_CONTROLS::s_objectSettings[] = { #define RR APPEARANCE_CONTROLS::APPEARANCE_SETTING // Render Row abbreviation to reduce source width // text id tooltip opacity slider RR( _( "Tracks" ), LAYER_TRACKS, _( "Show tracks" ), true ), RR( _( "Vias" ), LAYER_VIAS, _( "Show all vias" ), true ), RR( _( "Pads" ), LAYER_PADS, _( "Show all pads" ), true ), RR( _( "Zones" ), LAYER_ZONES, _( "Show copper zones" ), true ), RR(), RR( _( "Footprints Front" ), LAYER_MOD_FR, _( "Show footprints that are on board's front" ) ), RR( _( "Footprints Back" ), LAYER_MOD_BK, _( "Show footprints that are on board's back" ) ), RR( _( "Through-hole Pads" ),LAYER_PADS_TH, _( "Show through-hole pads" ) ), RR( _( "Values" ), LAYER_MOD_VALUES, _( "Show footprint values" ) ), RR( _( "References" ), LAYER_MOD_REFERENCES, _( "Show footprint references" ) ), RR( _( "Footprint Text" ), LAYER_MOD_TEXT_FR, _( "Show all footprint text" ) ), RR( _( "Hidden Text" ), LAYER_MOD_TEXT_INVISIBLE, _( "Show footprint text marked as invisible" ) ), RR(), RR(), RR( _( "Ratsnest" ), LAYER_RATSNEST, _( "Show unconnected nets as a ratsnest") ), RR( _( "No-Connects" ), LAYER_NO_CONNECTS, _( "Show a marker on pads which have no net connected" ) ), RR( _( "DRC Warnings" ), LAYER_DRC_WARNING, _( "DRC violations with a Warning severity" ) ), RR( _( "DRC Errors" ), LAYER_DRC_ERROR, _( "DRC violations with an Error severity" ) ), RR( _( "DRC Exclusions" ), LAYER_DRC_EXCLUSION, _( "DRC violations which have been individually excluded" ) ), RR( _( "Anchors" ), LAYER_ANCHOR, _( "Show footprint and text origins as a cross" ) ), RR( _( "Drawing Sheet" ), LAYER_DRAWINGSHEET, _( "Show drawing sheet borders and title block" ) ), RR( _( "Grid" ), LAYER_GRID, _( "Show the (x,y) grid dots" ) ) }; /// These GAL layers are shown in the Objects tab in the footprint editor static std::set s_allowedInFpEditor = { LAYER_TRACKS, LAYER_VIAS, LAYER_PADS, LAYER_ZONES, LAYER_PADS_TH, LAYER_MOD_VALUES, LAYER_MOD_REFERENCES, LAYER_MOD_TEXT_FR, LAYER_MOD_TEXT_INVISIBLE, LAYER_GRID }; // These are the built-in layer presets that cannot be deleted LAYER_PRESET APPEARANCE_CONTROLS::presetNoLayers( _( "No Layers" ), LSET() ); LAYER_PRESET APPEARANCE_CONTROLS::presetAllLayers( _( "All Layers" ), LSET::AllLayersMask() ); LAYER_PRESET APPEARANCE_CONTROLS::presetAllCopper( _( "All Copper Layers" ), LSET::AllCuMask().set( Edge_Cuts ) ); LAYER_PRESET APPEARANCE_CONTROLS::presetInnerCopper( _( "Inner Copper Layers" ), LSET::InternalCuMask().set( Edge_Cuts ) ); LAYER_PRESET APPEARANCE_CONTROLS::presetFront( _( "Front Layers" ), LSET::FrontMask().set( Edge_Cuts ) ); LAYER_PRESET APPEARANCE_CONTROLS::presetFrontAssembly( _( "Front Assembly View" ), LSET::FrontAssembly().set( Edge_Cuts ), GAL_SET::DefaultVisible(), F_SilkS ); LAYER_PRESET APPEARANCE_CONTROLS::presetBack( _( "Back Layers" ), LSET::BackMask().set( Edge_Cuts ) ); LAYER_PRESET APPEARANCE_CONTROLS::presetBackAssembly( _( "Back Assembly View" ), LSET::BackAssembly().set( Edge_Cuts ), GAL_SET::DefaultVisible(), B_SilkS ); APPEARANCE_CONTROLS::APPEARANCE_CONTROLS( PCB_BASE_FRAME* aParent, wxWindow* aFocusOwner, bool aFpEditorMode ) : APPEARANCE_CONTROLS_BASE( aParent ), m_frame( aParent ), m_focusOwner( aFocusOwner ), m_board( nullptr ), m_isFpEditor( aFpEditorMode ), m_currentPreset( nullptr ), m_lastSelectedUserPreset( nullptr ), m_layerContextMenu( nullptr ) { int indicatorSize = ConvertDialogToPixels( wxSize( 6, 6 ) ).x; m_iconProvider = new ROW_ICON_PROVIDER( indicatorSize ); int pointSize = wxSystemSettings::GetFont( wxSYS_DEFAULT_GUI_FONT ).GetPointSize(); int screenHeight = wxSystemSettings::GetMetric( wxSYS_SCREEN_Y ); m_layerPanelColour = m_panelLayers->GetBackgroundColour().ChangeLightness( 110 ); m_layersOuterSizer = new wxBoxSizer( wxVERTICAL ); m_windowLayers->SetSizer( m_layersOuterSizer ); m_windowLayers->SetScrollRate( 0, 5 ); m_windowLayers->Bind( wxEVT_SET_FOCUS, &APPEARANCE_CONTROLS::OnSetFocus, this ); m_objectsOuterSizer = new wxBoxSizer( wxVERTICAL ); m_windowObjects->SetSizer( m_objectsOuterSizer ); m_windowObjects->SetScrollRate( 0, 5 ); m_windowObjects->Bind( wxEVT_SET_FOCUS, &APPEARANCE_CONTROLS::OnSetFocus, this ); wxFont infoFont = KIUI::GetInfoFont( this ); m_staticTextNets->SetFont( infoFont ); m_staticTextNetClasses->SetFont( infoFont ); m_panelLayers->SetFont( infoFont ); m_windowLayers->SetFont( infoFont ); m_windowObjects->SetFont( infoFont ); m_presetsLabel->SetFont( infoFont ); createControls(); m_btnNetInspector->SetBitmap( KiBitmap( BITMAPS::list_nets_16 ) ); m_btnNetInspector->SetPadding( 2 ); m_btnConfigureNetClasses->SetBitmap( KiBitmap( BITMAPS::options_generic_16 ) ); m_btnConfigureNetClasses->SetPadding( 2 ); m_txtNetFilter->SetHint( _( "Filter nets" ) ); if( screenHeight <= 900 && pointSize >= indicatorSize ) pointSize = pointSize * 8 / 10; m_pointSize = pointSize; wxFont font = m_notebook->GetFont(); #ifdef __WXMAC__ font.SetPointSize( m_pointSize ); m_notebook->SetFont( font ); #endif auto setHighContrastMode = [&]( HIGH_CONTRAST_MODE aMode ) { PCB_DISPLAY_OPTIONS opts = m_frame->GetDisplayOptions(); opts.m_ContrastModeDisplay = aMode; m_frame->SetDisplayOptions( opts ); passOnFocus(); }; m_rbHighContrastNormal->Bind( wxEVT_RADIOBUTTON, [=]( wxCommandEvent& aEvent ) { setHighContrastMode( HIGH_CONTRAST_MODE::NORMAL ); } ); m_rbHighContrastDim->Bind( wxEVT_RADIOBUTTON, [=]( wxCommandEvent& aEvent ) { setHighContrastMode( HIGH_CONTRAST_MODE::DIMMED ); } ); m_rbHighContrastOff->Bind( wxEVT_RADIOBUTTON, [=]( wxCommandEvent& aEvent ) { setHighContrastMode( HIGH_CONTRAST_MODE::HIDDEN ); } ); m_cbLayerPresets->Bind( wxEVT_CHOICE, &APPEARANCE_CONTROLS::onLayerPresetChanged, this ); m_btnNetInspector->Bind( wxEVT_BUTTON, [&]( wxCommandEvent& aEvent ) { m_frame->GetToolManager()->RunAction( PCB_ACTIONS::listNets, true ); passOnFocus(); } ); m_btnConfigureNetClasses->Bind( wxEVT_BUTTON, [&]( wxCommandEvent& aEvent ) { // This panel should only be visible in the PCB_EDIT_FRAME anyway if( PCB_EDIT_FRAME* editframe = dynamic_cast( m_frame ) ) editframe->ShowBoardSetupDialog( _( "Net Classes" ) ); passOnFocus(); } ); m_cbFlipBoard->SetValue( m_frame->GetCanvas()->GetView()->IsMirroredX() ); m_cbFlipBoard->Bind( wxEVT_CHECKBOX, [&]( wxCommandEvent& aEvent ) { m_frame->GetToolManager()->RunAction( PCB_ACTIONS::flipBoard, true ); } ); m_toggleGridRenderer = new GRID_BITMAP_TOGGLE_RENDERER( KiBitmap( BITMAPS::visibility ), KiBitmap( BITMAPS::visibility_off ) ); m_netsGrid->RegisterDataType( wxT( "bool" ), m_toggleGridRenderer, new wxGridCellBoolEditor ); // TODO(JE) Update background color of swatch renderer when theme changes m_netsGrid->RegisterDataType( wxT( "COLOR4D" ), new GRID_CELL_COLOR_RENDERER( m_frame, SWATCH_SMALL ), new GRID_CELL_COLOR_SELECTOR( m_frame, m_netsGrid ) ); m_netsTable = new NET_GRID_TABLE( m_frame, m_panelNets->GetBackgroundColour() ); m_netsGrid->SetTable( m_netsTable, true ); m_netsGrid->SetColLabelSize( 0 ); m_netsGrid->SetSelectionMode( wxGrid::wxGridSelectRows ); m_netsGrid->SetSelectionForeground( m_netsGrid->GetDefaultCellTextColour() ); m_netsGrid->SetSelectionBackground( m_panelNets->GetBackgroundColour() ); const int cellPadding = 6; #ifdef __WXMAC__ const int rowHeightPadding = 5; #else const int rowHeightPadding = 3; #endif wxSize size = ConvertDialogToPixels( SWATCH_SIZE_SMALL_DU ); m_netsGrid->SetColSize( NET_GRID_TABLE::COL_COLOR, size.x + cellPadding ); size = KiBitmap( BITMAPS::visibility ).GetSize(); m_netsGrid->SetColSize( NET_GRID_TABLE::COL_VISIBILITY, size.x + cellPadding ); m_netsGrid->SetDefaultCellFont( font ); m_netsGrid->SetDefaultRowSize( font.GetPixelSize().y + rowHeightPadding ); m_netsGrid->GetGridWindow()->Bind( wxEVT_MOTION, &APPEARANCE_CONTROLS::OnNetGridMouseEvent, this ); // To handle middle click on color swatches m_netsGrid->GetGridWindow()->Bind( wxEVT_MIDDLE_UP, &APPEARANCE_CONTROLS::OnNetGridMouseEvent, this ); m_netsGrid->ShowScrollbars( wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT ); m_netclassScrolledWindow->ShowScrollbars( wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT ); if( m_isFpEditor ) m_notebook->RemovePage( 2 ); loadDefaultLayerPresets(); rebuildObjects(); OnBoardChanged(); // Grid visibility is loaded and set to the GAL before we are constructed SetObjectVisible( LAYER_GRID, m_frame->IsGridVisible() ); Bind( wxEVT_COMMAND_MENU_SELECTED, &APPEARANCE_CONTROLS::OnLayerContextMenu, this, ID_CHANGE_COLOR, ID_LAST_VALUE ); } APPEARANCE_CONTROLS::~APPEARANCE_CONTROLS() { delete m_iconProvider; } void APPEARANCE_CONTROLS::createControls() { int hotkey; wxString msg; wxFont infoFont = KIUI::GetInfoFont( this ); // Create layer display options m_paneLayerDisplayOptions = new WX_COLLAPSIBLE_PANE( m_panelLayers, wxID_ANY, _( "Layer Display Options" ) ); m_paneLayerDisplayOptions->Collapse(); m_paneLayerDisplayOptions->SetBackgroundColour( m_notebook->GetThemeBackgroundColour() ); wxWindow* layerDisplayPane = m_paneLayerDisplayOptions->GetPane(); wxBoxSizer* layerDisplayOptionsSizer; layerDisplayOptionsSizer = new wxBoxSizer( wxVERTICAL ); hotkey = PCB_ACTIONS::highContrastModeCycle.GetHotKey(); if( hotkey ) msg = wxString::Format( _( "Inactive layers (%s):" ), KeyNameFromKeyCode( hotkey ) ); else msg = _( "Inactive layers:" ); m_inactiveLayersLabel = new wxStaticText( layerDisplayPane, wxID_ANY, msg ); m_inactiveLayersLabel->SetFont( infoFont ); m_inactiveLayersLabel->Wrap( -1 ); layerDisplayOptionsSizer->Add( m_inactiveLayersLabel, 0, wxEXPAND | wxBOTTOM | wxLEFT, 2 ); wxBoxSizer* contrastModeSizer; contrastModeSizer = new wxBoxSizer( wxHORIZONTAL ); m_rbHighContrastNormal = new wxRadioButton( layerDisplayPane, wxID_ANY, _( "Normal" ), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); m_rbHighContrastNormal->SetFont( infoFont ); m_rbHighContrastNormal->SetValue( true ); m_rbHighContrastNormal->SetToolTip( _( "Inactive layers will be shown in full color" ) ); contrastModeSizer->Add( m_rbHighContrastNormal, 0, wxRIGHT, 5 ); contrastModeSizer->AddStretchSpacer(); m_rbHighContrastDim = new wxRadioButton( layerDisplayPane, wxID_ANY, _( "Dim" ) ); m_rbHighContrastDim->SetFont( infoFont ); m_rbHighContrastDim->SetToolTip( _( "Inactive layers will be dimmed" ) ); contrastModeSizer->Add( m_rbHighContrastDim, 0, wxRIGHT, 5 ); contrastModeSizer->AddStretchSpacer(); m_rbHighContrastOff = new wxRadioButton( layerDisplayPane, wxID_ANY, _( "Hide" ) ); m_rbHighContrastOff->SetFont( infoFont ); m_rbHighContrastOff->SetToolTip( _( "Inactive layers will be hidden" ) ); contrastModeSizer->Add( m_rbHighContrastOff, 0, 0, 5 ); contrastModeSizer->AddStretchSpacer(); layerDisplayOptionsSizer->Add( contrastModeSizer, 0, wxEXPAND, 5 ); m_layerDisplaySeparator = new wxStaticLine( layerDisplayPane, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL ); layerDisplayOptionsSizer->Add( m_layerDisplaySeparator, 0, wxEXPAND | wxTOP | wxBOTTOM, 5 ); m_cbFlipBoard = new wxCheckBox( layerDisplayPane, wxID_ANY, _( "Flip board view" ) ); m_cbFlipBoard->SetFont( infoFont ); layerDisplayOptionsSizer->Add( m_cbFlipBoard, 0, 0, 5 ); layerDisplayPane->SetSizer( layerDisplayOptionsSizer ); layerDisplayPane->Layout(); layerDisplayOptionsSizer->Fit( layerDisplayPane ); m_panelLayersSizer->Add( m_paneLayerDisplayOptions, 0, wxEXPAND | wxTOP, 5 ); m_paneLayerDisplayOptions->Bind( WX_COLLAPSIBLE_PANE_CHANGED, [&]( wxCommandEvent& aEvent ) { Freeze(); m_panelLayers->Fit(); m_sizerOuter->Layout(); Thaw(); } ); // Create net display options m_paneNetDisplayOptions = new WX_COLLAPSIBLE_PANE( m_panelNetsAndClasses, wxID_ANY, _( "Net Display Options" ) ); m_paneNetDisplayOptions->Collapse(); m_paneNetDisplayOptions->SetBackgroundColour( m_notebook->GetThemeBackgroundColour() ); wxWindow* netDisplayPane = m_paneNetDisplayOptions->GetPane(); wxBoxSizer* netDisplayOptionsSizer = new wxBoxSizer( wxVERTICAL ); //// Net color mode hotkey = PCB_ACTIONS::netColorModeCycle.GetHotKey(); if( hotkey ) msg = wxString::Format( _( "Net colors (%s):" ), KeyNameFromKeyCode( hotkey ) ); else msg = _( "Net colors:" ); m_txtNetDisplayTitle = new wxStaticText( netDisplayPane, wxID_ANY, msg ); m_txtNetDisplayTitle->SetFont( infoFont ); m_txtNetDisplayTitle->Wrap( -1 ); m_txtNetDisplayTitle->SetToolTip( _( "Choose when to show net and netclass colors" ) ); netDisplayOptionsSizer->Add( m_txtNetDisplayTitle, 0, wxEXPAND | wxBOTTOM | wxLEFT, 2 ); wxBoxSizer* netColorSizer = new wxBoxSizer( wxHORIZONTAL ); m_rbNetColorAll = new wxRadioButton( netDisplayPane, wxID_ANY, _( "All" ), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); m_rbNetColorAll->SetFont( infoFont ); m_rbNetColorAll->SetToolTip( _( "Net and netclass colors are shown on all copper items" ) ); netColorSizer->Add( m_rbNetColorAll, 0, wxRIGHT, 5 ); netColorSizer->AddStretchSpacer(); m_rbNetColorRatsnest = new wxRadioButton( netDisplayPane, wxID_ANY, _( "Ratsnest" ) ); m_rbNetColorRatsnest->SetFont( infoFont ); m_rbNetColorRatsnest->SetValue( true ); m_rbNetColorRatsnest->SetToolTip( _( "Net and netclass colors are shown on the ratsnest only" ) ); netColorSizer->Add( m_rbNetColorRatsnest, 0, wxRIGHT, 5 ); netColorSizer->AddStretchSpacer(); m_rbNetColorOff = new wxRadioButton( netDisplayPane, wxID_ANY, _( "None" ) ); m_rbNetColorOff->SetFont( infoFont ); m_rbNetColorOff->SetToolTip( _( "Net and netclass colors are not shown" ) ); netColorSizer->Add( m_rbNetColorOff, 0, 0, 5 ); netDisplayOptionsSizer->Add( netColorSizer, 0, wxEXPAND | wxBOTTOM, 5 ); //// Ratsnest display hotkey = PCB_ACTIONS::ratsnestModeCycle.GetHotKey(); if( hotkey ) msg = wxString::Format( _( "Ratsnest display (%s):" ), KeyNameFromKeyCode( hotkey ) ); else msg = _( "Ratsnest display:" ); m_txtRatsnestVisibility = new wxStaticText( netDisplayPane, wxID_ANY, msg ); m_txtRatsnestVisibility->SetFont( infoFont ); m_txtRatsnestVisibility->Wrap( -1 ); m_txtRatsnestVisibility->SetToolTip( _( "Choose which ratsnest lines to display" ) ); netDisplayOptionsSizer->Add( m_txtRatsnestVisibility, 0, wxEXPAND | wxBOTTOM | wxLEFT, 2 ); wxBoxSizer* ratsnestDisplayModeSizer = new wxBoxSizer( wxHORIZONTAL ); m_rbRatsnestAllLayers = new wxRadioButton( netDisplayPane, wxID_ANY, _( "All" ), wxDefaultPosition, wxDefaultSize, wxRB_GROUP ); m_rbRatsnestAllLayers->SetFont( infoFont ); m_rbRatsnestAllLayers->SetValue( true ); m_rbRatsnestAllLayers->SetToolTip( _( "Show ratsnest lines to items on all layers" ) ); ratsnestDisplayModeSizer->Add( m_rbRatsnestAllLayers, 0, wxRIGHT, 5 ); ratsnestDisplayModeSizer->AddStretchSpacer(); m_rbRatsnestVisLayers = new wxRadioButton( netDisplayPane, wxID_ANY, _( "Visible layers" ) ); m_rbRatsnestVisLayers->SetFont( infoFont ); m_rbRatsnestVisLayers->SetToolTip( _( "Show ratsnest lines to items on visible layers" ) ); ratsnestDisplayModeSizer->Add( m_rbRatsnestVisLayers, 0, wxRIGHT, 5 ); ratsnestDisplayModeSizer->AddStretchSpacer(); m_rbRatsnestNone = new wxRadioButton( netDisplayPane, wxID_ANY, _( "None" ) ); m_rbRatsnestNone->SetFont( infoFont ); m_rbRatsnestNone->SetToolTip( _( "Hide all ratsnest lines" ) ); ratsnestDisplayModeSizer->Add( m_rbRatsnestNone, 0, 0, 5 ); netDisplayOptionsSizer->Add( ratsnestDisplayModeSizer, 0, wxEXPAND | wxBOTTOM, 5 ); //// netDisplayPane->SetSizer( netDisplayOptionsSizer ); netDisplayPane->Layout(); netDisplayOptionsSizer->Fit( netDisplayPane ); m_netsTabOuterSizer->Add( m_paneNetDisplayOptions, 0, wxEXPAND | wxTOP, 5 ); m_paneNetDisplayOptions->Bind( WX_COLLAPSIBLE_PANE_CHANGED, [&]( wxCommandEvent& aEvent ) { Freeze(); m_panelNetsAndClasses->Fit(); m_sizerOuter->Layout(); passOnFocus(); Thaw(); } ); m_rbNetColorAll->Bind( wxEVT_RADIOBUTTON, &APPEARANCE_CONTROLS::onNetColorMode, this ); m_rbNetColorOff->Bind( wxEVT_RADIOBUTTON, &APPEARANCE_CONTROLS::onNetColorMode, this ); m_rbNetColorRatsnest->Bind( wxEVT_RADIOBUTTON, &APPEARANCE_CONTROLS::onNetColorMode, this ); m_rbRatsnestAllLayers->Bind( wxEVT_RADIOBUTTON, &APPEARANCE_CONTROLS::onRatsnestMode, this ); m_rbRatsnestVisLayers->Bind( wxEVT_RADIOBUTTON, &APPEARANCE_CONTROLS::onRatsnestMode, this ); m_rbRatsnestNone->Bind( wxEVT_RADIOBUTTON, &APPEARANCE_CONTROLS::onRatsnestMode, this ); } wxSize APPEARANCE_CONTROLS::GetBestSize() const { wxSize size( 220, 480 ); // TODO(JE) appropriate logic return size; } void APPEARANCE_CONTROLS::OnNotebookPageChanged( wxNotebookEvent& aEvent ) { // Work around wxMac issue where the notebook pages are blank #ifdef __WXMAC__ int page = aEvent.GetSelection(); if( page >= 0 ) m_notebook->ChangeSelection( static_cast( page ) ); #endif #ifndef __WXMSW__ // Because wxWidgets is broken and will send click events to children of the collapsible // panes even if they are collapsed without this Freeze(); m_panelLayers->Fit(); m_panelNetsAndClasses->Fit(); m_sizerOuter->Layout(); Thaw(); #endif Bind( wxEVT_IDLE, &APPEARANCE_CONTROLS::idleFocusHandler, this ); } void APPEARANCE_CONTROLS::idleFocusHandler( wxIdleEvent& aEvent ) { passOnFocus(); Unbind( wxEVT_IDLE, &APPEARANCE_CONTROLS::idleFocusHandler, this ); } void APPEARANCE_CONTROLS::OnSetFocus( wxFocusEvent& aEvent ) { #ifdef __WXMSW__ // In wxMSW, buttons won't process events unless they have focus, so we'll let it take the // focus and give it back to the parent in the button event handler. if( wxBitmapButton* btn = dynamic_cast( aEvent.GetEventObject() ) ) { wxCommandEvent evt( wxEVT_BUTTON ); wxPostEvent( btn, evt ); } #endif passOnFocus(); aEvent.Skip(); } void APPEARANCE_CONTROLS::OnSize( wxSizeEvent& aEvent ) { aEvent.Skip(); } void APPEARANCE_CONTROLS::OnNetGridClick( wxGridEvent& event ) { int row = event.GetRow(); int col = event.GetCol(); switch( col ) { case NET_GRID_TABLE::COL_VISIBILITY: m_netsTable->SetValueAsBool( row, col, !m_netsTable->GetValueAsBool( row, col ) ); m_netsGrid->ForceRefresh(); break; default: break; } } void APPEARANCE_CONTROLS::OnNetGridDoubleClick( wxGridEvent& event ) { int row = event.GetRow(); int col = event.GetCol(); switch( col ) { case NET_GRID_TABLE::COL_COLOR: m_netsGrid->GetCellEditor( row, col )->BeginEdit( row, col, m_netsGrid ); break; default: break; } } void APPEARANCE_CONTROLS::OnNetGridRightClick( wxGridEvent& event ) { m_netsGrid->SelectRow( event.GetRow() ); wxString netName = m_netsGrid->GetCellValue( event.GetRow(), NET_GRID_TABLE::COL_LABEL ); wxMenu menu; menu.Append( new wxMenuItem( &menu, ID_SET_NET_COLOR, _( "Set Net Color" ), wxEmptyString, wxITEM_NORMAL ) ); menu.Append( new wxMenuItem( &menu, ID_HIGHLIGHT_NET, wxString::Format( _( "Highlight %s" ), netName ), wxEmptyString, wxITEM_NORMAL ) ); menu.Append( new wxMenuItem( &menu, ID_SELECT_NET, wxString::Format( _( "Select Tracks and Vias in %s" ), netName ), wxEmptyString, wxITEM_NORMAL ) ); menu.Append( new wxMenuItem( &menu, ID_DESELECT_NET, wxString::Format( _( "Unselect Tracks and Vias in %s" ), netName ), wxEmptyString, wxITEM_NORMAL ) ); menu.AppendSeparator(); menu.Append( new wxMenuItem( &menu, ID_SHOW_ALL_NETS, _( "Show All Nets" ), wxEmptyString, wxITEM_NORMAL ) ); menu.Append( new wxMenuItem( &menu, ID_HIDE_OTHER_NETS, _( "Hide All Other Nets" ), wxEmptyString, wxITEM_NORMAL ) ); menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &APPEARANCE_CONTROLS::onNetContextMenu, this ); PopupMenu( &menu ); } void APPEARANCE_CONTROLS::OnNetGridMouseEvent( wxMouseEvent& aEvent ) { wxPoint pos = m_netsGrid->CalcUnscrolledPosition( aEvent.GetPosition() ); wxGridCellCoords cell = m_netsGrid->XYToCell( pos ); if( aEvent.Moving() || aEvent.Entering() ) { aEvent.Skip(); if( !cell ) { m_netsGrid->GetGridWindow()->UnsetToolTip(); return; } if( cell == m_hoveredCell ) return; m_hoveredCell = cell; NET_GRID_ENTRY& net = m_netsTable->GetEntry( cell.GetRow() ); wxString name = net.name; wxString showOrHide = net.visible ? _( "Click to hide ratsnest for %s" ) : _( "Click to show ratsnest for %s" ); wxString tip; if( cell.GetCol() == NET_GRID_TABLE::COL_VISIBILITY ) { tip.Printf( showOrHide, name ); } else if( cell.GetCol() == NET_GRID_TABLE::COL_COLOR ) { tip = _( "Double click (or middle click) to change color; " "right click for more actions" ); } m_netsGrid->GetGridWindow()->SetToolTip( tip ); } else if( aEvent.Leaving() ) { m_netsGrid->UnsetToolTip(); aEvent.Skip(); } else if( aEvent.Dragging() ) { // not allowed CallAfter( [&]() { m_netsGrid->ClearSelection(); } ); } else if( aEvent.ButtonUp( wxMOUSE_BTN_MIDDLE ) && !!cell ) { int row = cell.GetRow(); int col = cell.GetCol(); if(col == NET_GRID_TABLE::COL_COLOR ) m_netsGrid->GetCellEditor( row, col )->BeginEdit( row, col, m_netsGrid ); aEvent.Skip(); } else { aEvent.Skip(); } } void APPEARANCE_CONTROLS::OnBoardChanged() { Freeze(); rebuildLayers(); rebuildLayerContextMenu(); syncColorsAndVisibility(); syncObjectSettings(); rebuildNets(); rebuildLayerPresetsWidget(); syncLayerPresetSelection(); UpdateDisplayOptions(); m_board = m_frame->GetBoard(); if( m_board ) m_board->AddListener( this ); Thaw(); Refresh(); } void APPEARANCE_CONTROLS::OnBoardNetSettingsChanged( BOARD& aBoard ) { handleBoardItemsChanged(); } void APPEARANCE_CONTROLS::OnNetVisibilityChanged( int aNetCode, bool aVisibility ) { int row = m_netsTable->GetRowByNetcode( aNetCode ); if( row >= 0 ) { m_netsTable->SetValueAsBool( row, NET_GRID_TABLE::COL_VISIBILITY, aVisibility ); m_netsGrid->ForceRefresh(); } } bool APPEARANCE_CONTROLS::doesBoardItemNeedRebuild( BOARD_ITEM* aBoardItem ) { return aBoardItem->Type() == PCB_NETINFO_T; } bool APPEARANCE_CONTROLS::doesBoardItemNeedRebuild( std::vector& aBoardItems ) { bool rebuild = std::any_of( aBoardItems.begin(), aBoardItems.end(), []( const BOARD_ITEM* a ) { return a->Type() == PCB_NETINFO_T; } ); return rebuild; } void APPEARANCE_CONTROLS::OnBoardItemAdded( BOARD& aBoard, BOARD_ITEM* aItem ) { if( doesBoardItemNeedRebuild( aItem ) ) handleBoardItemsChanged(); } void APPEARANCE_CONTROLS::OnBoardItemsAdded( BOARD& aBoard, std::vector& aItems ) { if( doesBoardItemNeedRebuild( aItems ) ) handleBoardItemsChanged(); } void APPEARANCE_CONTROLS::OnBoardItemRemoved( BOARD& aBoard, BOARD_ITEM* aItem ) { if( doesBoardItemNeedRebuild( aItem ) ) handleBoardItemsChanged(); } void APPEARANCE_CONTROLS::OnBoardItemsRemoved( BOARD& aBoard, std::vector& aItems ) { if( doesBoardItemNeedRebuild( aItems ) ) handleBoardItemsChanged(); } void APPEARANCE_CONTROLS::OnBoardItemChanged( BOARD& aBoard, BOARD_ITEM* aItem ) { if( doesBoardItemNeedRebuild( aItem ) ) handleBoardItemsChanged(); } void APPEARANCE_CONTROLS::OnBoardItemsChanged( BOARD& aBoard, std::vector& aItems ) { if( doesBoardItemNeedRebuild( aItems ) ) handleBoardItemsChanged(); } void APPEARANCE_CONTROLS::handleBoardItemsChanged() { Freeze(); rebuildNets(); Thaw(); } void APPEARANCE_CONTROLS::OnColorThemeChanged() { syncColorsAndVisibility(); syncObjectSettings(); } void APPEARANCE_CONTROLS::OnLayerChanged() { for( const std::unique_ptr& setting : m_layerSettings ) { setting->ctl_panel->SetBackgroundColour( m_layerPanelColour ); setting->ctl_indicator->SetIndicatorState( ROW_ICON_PROVIDER::STATE::OFF ); } wxChar r, g, b; r = m_layerPanelColour.Red(); g = m_layerPanelColour.Green(); b = m_layerPanelColour.Blue(); if( r < 240 || g < 240 || b < 240 ) { r = wxChar( std::min( (int) r + 15, 255 ) ); g = wxChar( std::min( (int) g + 15, 255 ) ); b = wxChar( std::min( (int) b + 15, 255 ) ); } else { r = wxChar( std::max( (int) r - 15, 0 ) ); g = wxChar( std::max( (int) g - 15, 0 ) ); b = wxChar( std::max( (int) b - 15, 0 ) ); } PCB_LAYER_ID current = m_frame->GetActiveLayer(); if( !m_layerSettingsMap.count( current ) ) { wxASSERT( m_layerSettingsMap.count( F_Cu ) ); current = F_Cu; } APPEARANCE_SETTING* newSetting = m_layerSettingsMap[ current ]; newSetting->ctl_panel->SetBackgroundColour( wxColour( r, g, b ) ); newSetting->ctl_indicator->SetIndicatorState( ROW_ICON_PROVIDER::STATE::ON ); Refresh(); } void APPEARANCE_CONTROLS::SetLayerVisible( LAYER_NUM aLayer, bool isVisible ) { LSET visible = getVisibleLayers(); PCB_LAYER_ID layer = ToLAYER_ID( aLayer ); if( visible.test( layer ) == isVisible ) return; visible.set( layer, isVisible ); setVisibleLayers( visible ); m_frame->GetCanvas()->GetView()->SetLayerVisible( layer, isVisible ); syncColorsAndVisibility(); } void APPEARANCE_CONTROLS::SetObjectVisible( GAL_LAYER_ID aLayer, bool isVisible ) { if( m_objectSettingsMap.count( aLayer ) ) { APPEARANCE_SETTING* setting = m_objectSettingsMap.at( aLayer ); setting->ctl_visibility->SetValue( isVisible ); } m_frame->GetBoard()->SetElementVisibility( aLayer, isVisible ); m_frame->GetCanvas()->GetView()->SetLayerVisible( aLayer, isVisible ); m_frame->GetCanvas()->Refresh(); } void APPEARANCE_CONTROLS::setVisibleLayers( LSET aLayers ) { if( m_isFpEditor ) { KIGFX::VIEW* view = m_frame->GetCanvas()->GetView(); for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() ) view->SetLayerVisible( layer, aLayers.Contains( layer ) ); } else { m_frame->GetBoard()->SetVisibleLayers( aLayers ); } } void APPEARANCE_CONTROLS::setVisibleObjects( GAL_SET aLayers ) { if( m_isFpEditor ) { KIGFX::VIEW* view = m_frame->GetCanvas()->GetView(); for( size_t i = 0; i < GAL_LAYER_INDEX( LAYER_ZONE_START ); i++ ) view->SetLayerVisible( GAL_LAYER_ID_START + GAL_LAYER_ID( i ), aLayers.test( i ) ); } else { // Ratsnest visibility is controlled by the ratsnest option, and not by the preset if( m_frame->IsType( FRAME_PCB_EDITOR ) ) { PCB_DISPLAY_OPTIONS opt = m_frame->GetDisplayOptions(); aLayers.set( LAYER_RATSNEST, opt.m_ShowGlobalRatsnest ); } m_frame->GetBoard()->SetVisibleElements( aLayers ); } } LSET APPEARANCE_CONTROLS::getVisibleLayers() { if( m_isFpEditor ) { KIGFX::VIEW* view = m_frame->GetCanvas()->GetView(); LSET set; for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() ) set.set( layer, view->IsLayerVisible( layer ) ); return set; } else { return m_frame->GetBoard()->GetVisibleLayers(); } } GAL_SET APPEARANCE_CONTROLS::getVisibleObjects() { if( m_isFpEditor ) { KIGFX::VIEW* view = m_frame->GetCanvas()->GetView(); GAL_SET set; set.reset(); for( size_t i = 0; i < set.size(); i++ ) set.set( i, view->IsLayerVisible( GAL_LAYER_ID_START + GAL_LAYER_ID( i ) ) ); return set; } else { return m_frame->GetBoard()->GetVisibleElements(); } } void APPEARANCE_CONTROLS::OnLayerAlphaChanged() { // TODO(JE) Is this even needed if the layer alphas are getting directly updated? // Maybe we just need the "down" arrow to indicate if the alpha is below 1 #if 0 static constexpr double alphaEpsilon = 0.04; PCB_LAYER_ID current = m_frame->GetActiveLayer(); COLOR_SETTINGS* theme = m_frame->GetColorSettings(); KIGFX::PCB_PAINTER* painter = static_cast( m_frame->GetCanvas()->GetView()->GetPainter() ); KIGFX::PCB_RENDER_SETTINGS* rs = painter->GetSettings(); for( APPEARANCE_SETTING& setting : m_layerSettings ) { if( !setting.ctl_indicator ) continue; COLOR4D layerColor = theme->GetColor( setting.id ); COLOR4D screenColor = rs->GetLayerColor( setting.id ); if( std::abs( screenColor.a - layerColor.a ) > alphaEpsilon ) { if( screenColor.a < layerColor.a ) setting.ctl_indicator->SetIndicatorState( ROW_ICON_PROVIDER::STATE::DOWN ); else setting.ctl_indicator->SetIndicatorState( ROW_ICON_PROVIDER::STATE::UP ); } else { setting.ctl_indicator->SetIndicatorState( setting.id == current ? ROW_ICON_PROVIDER::STATE::ON : ROW_ICON_PROVIDER::STATE::OFF ); } } #endif } void APPEARANCE_CONTROLS::UpdateDisplayOptions() { const PCB_DISPLAY_OPTIONS& options = m_frame->GetDisplayOptions(); switch( options.m_ContrastModeDisplay ) { case HIGH_CONTRAST_MODE::NORMAL: m_rbHighContrastNormal->SetValue( true ); break; case HIGH_CONTRAST_MODE::DIMMED: m_rbHighContrastDim->SetValue( true ); break; case HIGH_CONTRAST_MODE::HIDDEN: m_rbHighContrastOff->SetValue( true ); break; } switch( options.m_NetColorMode ) { case NET_COLOR_MODE::ALL: m_rbNetColorAll->SetValue( true ); break; case NET_COLOR_MODE::RATSNEST: m_rbNetColorRatsnest->SetValue( true ); break; case NET_COLOR_MODE::OFF: m_rbNetColorOff->SetValue( true ); break; } m_cbFlipBoard->SetValue( m_frame->GetCanvas()->GetView()->IsMirroredX() ); if( !m_isFpEditor ) { if( !options.m_ShowGlobalRatsnest ) m_rbRatsnestNone->SetValue( true ); else if( options.m_RatsnestMode == RATSNEST_MODE::ALL ) m_rbRatsnestAllLayers->SetValue( true ); else m_rbRatsnestVisLayers->SetValue( true ); wxASSERT( m_objectSettingsMap.count( LAYER_RATSNEST ) ); APPEARANCE_SETTING* ratsnest = m_objectSettingsMap.at( LAYER_RATSNEST ); ratsnest->ctl_visibility->SetValue( options.m_ShowGlobalRatsnest ); } } std::vector APPEARANCE_CONTROLS::GetUserLayerPresets() const { std::vector ret; for( const std::pair& pair : m_layerPresets ) { if( !pair.second.readOnly ) ret.emplace_back( pair.second ); } return ret; } void APPEARANCE_CONTROLS::SetUserLayerPresets( std::vector& aPresetList ) { // Reset to defaults loadDefaultLayerPresets(); for( const LAYER_PRESET& preset : aPresetList ) { if( m_layerPresets.count( preset.name ) ) continue; m_layerPresets[preset.name] = preset; m_presetMRU.Add( preset.name ); } rebuildLayerPresetsWidget(); } void APPEARANCE_CONTROLS::loadDefaultLayerPresets() { m_layerPresets.clear(); m_presetMRU.clear(); // Load the read-only defaults for( const LAYER_PRESET& preset : { presetAllLayers, presetAllCopper, presetInnerCopper, presetFront, presetFrontAssembly, presetBack, presetBackAssembly } ) { m_layerPresets[preset.name] = preset; m_layerPresets[preset.name].readOnly = true; m_presetMRU.Add( preset.name ); } } void APPEARANCE_CONTROLS::ApplyLayerPreset( const wxString& aPresetName ) { updateLayerPresetSelection( aPresetName ); wxCommandEvent dummy; onLayerPresetChanged( dummy ); } void APPEARANCE_CONTROLS::ApplyLayerPreset( const LAYER_PRESET& aPreset ) { if( m_layerPresets.count( aPreset.name ) ) m_currentPreset = &m_layerPresets[aPreset.name]; else m_currentPreset = nullptr; m_lastSelectedUserPreset = ( m_currentPreset && !m_currentPreset->readOnly ) ? m_currentPreset : nullptr; updateLayerPresetSelection( aPreset.name ); doApplyLayerPreset( aPreset ); } void APPEARANCE_CONTROLS::rebuildLayers() { BOARD* board = m_frame->GetBoard(); LSET enabled = board->GetEnabledLayers(); LSET visible = getVisibleLayers(); COLOR_SETTINGS* theme = m_frame->GetColorSettings(); COLOR4D bgColor = theme->GetColor( LAYER_PCB_BACKGROUND ); bool readOnly = theme->IsReadOnly(); #ifdef __WXMAC__ wxSizerItem* m_windowLayersSizerItem = m_panelLayersSizer->GetItem( m_windowLayers ); m_windowLayersSizerItem->SetFlag( m_windowLayersSizerItem->GetFlag() & ~wxTOP ); #endif auto appendLayer = [&]( std::unique_ptr& aSetting ) { int layer = aSetting->id; wxPanel* panel = new wxPanel( m_windowLayers, layer ); wxBoxSizer* sizer = new wxBoxSizer( wxHORIZONTAL ); panel->SetSizer( sizer ); panel->SetBackgroundColour( m_layerPanelColour ); aSetting->visible = visible[layer]; // TODO(JE) consider restyling this indicator INDICATOR_ICON* indicator = new INDICATOR_ICON( panel, *m_iconProvider, ROW_ICON_PROVIDER::STATE::OFF, layer ); COLOR_SWATCH* swatch = new COLOR_SWATCH( panel, COLOR4D::UNSPECIFIED, layer, bgColor, theme->GetColor( layer ), SWATCH_SMALL ); swatch->SetToolTip( _( "Double click or middle click for color change, " "right click for menu" ) ); BITMAP_TOGGLE* btn_visible = new BITMAP_TOGGLE( panel, layer, KiBitmap( BITMAPS::visibility ), KiBitmap( BITMAPS::visibility_off ), aSetting->visible ); btn_visible->SetToolTip( _( "Show or hide this layer" ) ); wxStaticText* label = new wxStaticText( panel, layer, aSetting->label ); label->Wrap( -1 ); label->SetToolTip( aSetting->tooltip ); sizer->AddSpacer( 1 ); sizer->Add( indicator, 0, wxALIGN_CENTER_VERTICAL | wxTOP, 2 ); sizer->AddSpacer( 5 ); sizer->Add( swatch, 0, wxALIGN_CENTER_VERTICAL | wxTOP, 2 ); sizer->AddSpacer( 6 ); sizer->Add( btn_visible, 0, wxALIGN_CENTER_VERTICAL | wxTOP, 2 ); sizer->AddSpacer( 5 ); sizer->Add( label, 1, wxALIGN_CENTER_VERTICAL | wxTOP, 2 ); m_layersOuterSizer->Add( panel, 0, wxEXPAND, 0 ); aSetting->ctl_panel = panel; aSetting->ctl_indicator = indicator; aSetting->ctl_visibility = btn_visible; aSetting->ctl_color = swatch; aSetting->ctl_text = label; panel->Bind( wxEVT_LEFT_DOWN, &APPEARANCE_CONTROLS::onLayerLeftClick, this ); indicator->Bind( wxEVT_LEFT_DOWN, &APPEARANCE_CONTROLS::onLayerLeftClick, this ); swatch->Bind( wxEVT_LEFT_DOWN, &APPEARANCE_CONTROLS::onLayerLeftClick, this ); label->Bind( wxEVT_LEFT_DOWN, &APPEARANCE_CONTROLS::onLayerLeftClick, this ); btn_visible->Bind( TOGGLE_CHANGED, [&]( wxCommandEvent& aEvent ) { wxObject* btn = aEvent.GetEventObject(); int layId = static_cast( btn )->GetId(); bool isVisible = aEvent.GetInt(); wxASSERT( layId >= 0 && layId < PCB_LAYER_ID_COUNT ); if( m_isFpEditor && LSET::ForbiddenFootprintLayers().test( layId ) ) { static_cast( btn )->SetValue( !isVisible ); return; } onLayerVisibilityChanged( static_cast( layId ), isVisible, true ); } ); swatch->Bind( COLOR_SWATCH_CHANGED, &APPEARANCE_CONTROLS::OnColorSwatchChanged, this ); swatch->SetReadOnlyCallback(std::bind( &APPEARANCE_CONTROLS::onReadOnlySwatch, this ) ); swatch->SetReadOnly( readOnly ); panel->Bind( wxEVT_RIGHT_DOWN, &APPEARANCE_CONTROLS::rightClickHandler, this ); indicator->Bind( wxEVT_RIGHT_DOWN, &APPEARANCE_CONTROLS::rightClickHandler, this ); swatch->Bind( wxEVT_RIGHT_DOWN, &APPEARANCE_CONTROLS::rightClickHandler, this ); btn_visible->Bind( wxEVT_RIGHT_DOWN, &APPEARANCE_CONTROLS::rightClickHandler, this ); label->Bind( wxEVT_RIGHT_DOWN, &APPEARANCE_CONTROLS::rightClickHandler, this ); }; auto updateLayer = [&]( std::unique_ptr& aSetting ) { int layer = aSetting->id; aSetting->visible = visible[layer]; aSetting->ctl_panel->Show(); aSetting->ctl_panel->SetId( layer ); aSetting->ctl_indicator->SetWindowID( layer ); aSetting->ctl_color->SetWindowID( layer ); aSetting->ctl_color->SetSwatchColor( theme->GetColor( layer ), false ); aSetting->ctl_visibility->SetWindowID( layer ); aSetting->ctl_text->SetLabelText( aSetting->label ); aSetting->ctl_text->SetId( layer ); aSetting->ctl_text->SetToolTip( aSetting->tooltip ); }; // technical layers are shown in this order: // Because they are static, wxGetTranslation must be explicitly // called for tooltips. static const struct { PCB_LAYER_ID layerId; wxString tooltip; } non_cu_seq[] = { { F_Adhes, _( "Adhesive on board's front" ) }, { B_Adhes, _( "Adhesive on board's back" ) }, { F_Paste, _( "Solder paste on board's front" ) }, { B_Paste, _( "Solder paste on board's back" ) }, { F_SilkS, _( "Silkscreen on board's front" ) }, { B_SilkS, _( "Silkscreen on board's back" ) }, { F_Mask, _( "Solder mask on board's front" ) }, { B_Mask, _( "Solder mask on board's back" ) }, { Dwgs_User, _( "Explanatory drawings" ) }, { Cmts_User, _( "Explanatory comments" ) }, { Eco1_User, _( "User defined meaning" ) }, { Eco2_User, _( "User defined meaning" ) }, { Edge_Cuts, _( "Board's perimeter definition" ) }, { Margin, _( "Board's edge setback outline" ) }, { F_CrtYd, _( "Footprint courtyards on board's front" ) }, { B_CrtYd, _( "Footprint courtyards on board's back" ) }, { F_Fab, _( "Footprint assembly on board's front" ) }, { B_Fab, _( "Footprint assembly on board's back" ) }, { User_1, _( "User defined layer 1" ) }, { User_2, _( "User defined layer 2" ) }, { User_3, _( "User defined layer 3" ) }, { User_4, _( "User defined layer 4" ) }, { User_5, _( "User defined layer 5" ) }, { User_6, _( "User defined layer 6" ) }, { User_7, _( "User defined layer 7" ) }, { User_8, _( "User defined layer 8" ) }, { User_9, _( "User defined layer 9" ) }, }; // There is a spacer added to the end of the list that we need to remove and re-add // after possibly adding additional layers if( m_layersOuterSizer->GetItemCount() > 0 ) { m_layersOuterSizer->Detach( m_layersOuterSizer->GetItemCount() - 1 ); } // Otherwise, this is the first time we are updating the control, so we need to attach // the handler else { // Add right click handling to show the context menu when clicking to the free area in // m_windowLayers (below the layer items) m_windowLayers->Bind( wxEVT_RIGHT_DOWN, &APPEARANCE_CONTROLS::rightClickHandler, this ); } std::size_t total_layers = enabled.CuStack().size(); for( const auto& entry : non_cu_seq ) { if( enabled[entry.layerId] ) total_layers++; } // Adds layers to the panel until we have enough to hold our total count while( total_layers > m_layerSettings.size() ) m_layerSettings.push_back( std::make_unique() ); // We never delete layers from the panel, only hide them. This saves us // having to recreate the (possibly) later with minimal overhead for( std::size_t ii = total_layers; ii < m_layerSettings.size(); ++ii ) { if( m_layerSettings[ii]->ctl_panel ) m_layerSettings[ii]->ctl_panel->Show( false ); } auto layer_it = m_layerSettings.begin(); // show all coppers first, with front on top, back on bottom, then technical layers for( LSEQ cu_stack = enabled.CuStack(); cu_stack; ++cu_stack, ++layer_it ) { PCB_LAYER_ID layer = *cu_stack; wxString dsc; switch( layer ) { case F_Cu: dsc = _( "Front copper layer" ); break; case B_Cu: dsc = _( "Back copper layer" ); break; default: dsc = _( "Inner copper layer" ); break; } std::unique_ptr& setting = *layer_it; setting->label = board->GetLayerName( layer ); setting->id = layer; setting->tooltip = dsc; if( setting->ctl_panel == nullptr ) appendLayer( setting ); else updateLayer( setting ); m_layerSettingsMap[layer] = setting.get(); if( m_isFpEditor && LSET::ForbiddenFootprintLayers().test( layer ) ) { setting->ctl_text->Disable(); setting->ctl_color->SetToolTip( wxEmptyString ); } } for( const auto& entry : non_cu_seq ) { PCB_LAYER_ID layer = entry.layerId; if( !enabled[layer] ) continue; std::unique_ptr& setting = *layer_it; setting->label = board->GetLayerName( layer ); setting->id = layer; setting->tooltip = entry.tooltip; if( setting->ctl_panel == nullptr ) appendLayer( setting ); else updateLayer( setting ); m_layerSettingsMap[layer] = setting.get(); if( m_isFpEditor && LSET::ForbiddenFootprintLayers().test( layer ) ) { setting->ctl_text->Disable(); setting->ctl_color->SetToolTip( wxEmptyString ); } ++layer_it; } m_layersOuterSizer->AddSpacer( 10 ); m_windowLayers->SetBackgroundColour( m_layerPanelColour ); m_windowLayers->Layout(); } void APPEARANCE_CONTROLS::rebuildLayerContextMenu() { delete m_layerContextMenu; m_layerContextMenu = new wxMenu; AddMenuItem( m_layerContextMenu, ID_SHOW_ALL_COPPER_LAYERS, _( "Show All Copper Layers" ), KiBitmap( BITMAPS::show_all_copper_layers ) ); AddMenuItem( m_layerContextMenu, ID_HIDE_ALL_COPPER_LAYERS, _( "Hide All Copper Layers" ), KiBitmap( BITMAPS::show_no_copper_layers ) ); m_layerContextMenu->AppendSeparator(); AddMenuItem( m_layerContextMenu, ID_HIDE_ALL_BUT_ACTIVE, _( "Hide All Layers But Active" ), KiBitmap( BITMAPS::select_w_layer ) ); m_layerContextMenu->AppendSeparator(); AddMenuItem( m_layerContextMenu, ID_SHOW_ALL_NON_COPPER, _( "Show All Non Copper Layers" ), KiBitmap( BITMAPS::show_no_copper_layers ) ); AddMenuItem( m_layerContextMenu, ID_HIDE_ALL_NON_COPPER, _( "Hide All Non Copper Layers" ), KiBitmap( BITMAPS::show_all_copper_layers ) ); m_layerContextMenu->AppendSeparator(); AddMenuItem( m_layerContextMenu, ID_PRESET_ALL_LAYERS, _( "Show All Layers" ), KiBitmap( BITMAPS::show_all_layers ) ); AddMenuItem( m_layerContextMenu, ID_PRESET_NO_LAYERS, _( "Hide All Layers" ), KiBitmap( BITMAPS::show_no_layers ) ); m_layerContextMenu->AppendSeparator(); AddMenuItem( m_layerContextMenu, ID_PRESET_FRONT_ASSEMBLY, _( "Show Only Front Assembly Layers" ), KiBitmap( BITMAPS::show_front_assembly_layers ) ); AddMenuItem( m_layerContextMenu, ID_PRESET_FRONT, _( "Show Only Front Layers" ), KiBitmap( BITMAPS::show_all_front_layers ) ); // Only show the internal layer option if internal layers are enabled if( m_frame->GetBoard()->GetCopperLayerCount() > 2 ) { AddMenuItem( m_layerContextMenu, ID_PRESET_INNER_COPPER, _( "Show Only Inner Layers" ), KiBitmap( BITMAPS::show_all_copper_layers ) ); } AddMenuItem( m_layerContextMenu, ID_PRESET_BACK, _( "Show Only Back Layers" ), KiBitmap( BITMAPS::show_all_back_layers ) ); AddMenuItem( m_layerContextMenu, ID_PRESET_BACK_ASSEMBLY, _( "Show Only Back Assembly Layers" ), KiBitmap( BITMAPS::show_back_assembly_layers ) ); } void APPEARANCE_CONTROLS::OnLayerContextMenu( wxCommandEvent& aEvent ) { BOARD* board = m_frame->GetBoard(); LSET visible = getVisibleLayers(); PCB_LAYER_ID current = m_frame->GetActiveLayer(); switch( aEvent.GetId() ) { case ID_PRESET_NO_LAYERS: ApplyLayerPreset( presetNoLayers ); return; case ID_PRESET_ALL_LAYERS: ApplyLayerPreset( presetAllLayers ); return; case ID_SHOW_ALL_COPPER_LAYERS: { visible |= presetAllCopper.layers; setVisibleLayers( visible ); break; } case ID_HIDE_ALL_BUT_ACTIVE: ApplyLayerPreset( presetNoLayers ); SetLayerVisible( current, true ); break; case ID_HIDE_ALL_COPPER_LAYERS: { visible &= ~presetAllCopper.layers; if( !visible.test( current ) ) m_frame->SetActiveLayer( *visible.Seq().begin() ); setVisibleLayers( visible ); break; } case ID_HIDE_ALL_NON_COPPER: { visible &= presetAllCopper.layers; if( !visible.test( current ) ) m_frame->SetActiveLayer( *visible.Seq().begin() ); setVisibleLayers( visible ); break; } case ID_SHOW_ALL_NON_COPPER: { visible |= ~presetAllCopper.layers; setVisibleLayers( visible ); break; } case ID_PRESET_FRONT_ASSEMBLY: ApplyLayerPreset( presetFrontAssembly ); return; case ID_PRESET_FRONT: ApplyLayerPreset( presetFront ); return; case ID_PRESET_INNER_COPPER: ApplyLayerPreset( presetInnerCopper ); return; case ID_PRESET_BACK: ApplyLayerPreset( presetBack ); return; case ID_PRESET_BACK_ASSEMBLY: ApplyLayerPreset( presetBackAssembly ); return; } syncLayerPresetSelection(); syncColorsAndVisibility(); if( !m_isFpEditor ) m_frame->GetCanvas()->SyncLayersVisibility( board ); m_frame->GetCanvas()->Refresh(); } int APPEARANCE_CONTROLS::GetTabIndex() const { return m_notebook->GetSelection(); } void APPEARANCE_CONTROLS::SetTabIndex( int aTab ) { size_t max = m_notebook->GetPageCount(); if( aTab >= 0 && static_cast( aTab ) < max ) m_notebook->SetSelection( aTab ); } void APPEARANCE_CONTROLS::syncColorsAndVisibility() { COLOR_SETTINGS* theme = m_frame->GetColorSettings(); bool readOnly = theme->IsReadOnly(); LSET visible = getVisibleLayers(); GAL_SET objects = getVisibleObjects(); Freeze(); for( std::unique_ptr& setting : m_layerSettings ) { LAYER_NUM layer = setting->id; if( setting->ctl_visibility ) setting->ctl_visibility->SetValue( visible[layer] ); if( setting->ctl_color ) { const COLOR4D& color = theme->GetColor( layer ); setting->ctl_color->SetSwatchColor( color, false ); setting->ctl_color->SetReadOnly( readOnly ); } } for( std::unique_ptr& setting : m_objectSettings ) { GAL_LAYER_ID layer = static_cast( setting->id ); if( setting->ctl_visibility ) setting->ctl_visibility->SetValue( objects.Contains( layer ) ); if( setting->ctl_color ) { const COLOR4D& color = theme->GetColor( layer ); setting->ctl_color->SetSwatchColor( color, false ); setting->ctl_color->SetReadOnly( readOnly ); } } // Update indicators and panel background colors OnLayerChanged(); Thaw(); m_windowLayers->Refresh(); } void APPEARANCE_CONTROLS::onLayerLeftClick( wxMouseEvent& aEvent ) { auto eventSource = static_cast( aEvent.GetEventObject() ); PCB_LAYER_ID layer = ToLAYER_ID( eventSource->GetId() ); if( m_isFpEditor && LSET::ForbiddenFootprintLayers().test( layer ) ) return; m_frame->SetActiveLayer( layer ); passOnFocus(); } void APPEARANCE_CONTROLS::rightClickHandler( wxMouseEvent& aEvent ) { wxASSERT( m_layerContextMenu ); PopupMenu( m_layerContextMenu ); passOnFocus(); }; void APPEARANCE_CONTROLS::onLayerVisibilityChanged( PCB_LAYER_ID aLayer, bool isVisible, bool isFinal ) { LSET visibleLayers = getVisibleLayers(); if( visibleLayers.test( aLayer ) != isVisible ) { visibleLayers.set( aLayer, isVisible ); setVisibleLayers( visibleLayers ); m_frame->GetCanvas()->GetView()->SetLayerVisible( aLayer, isVisible ); } syncLayerPresetSelection(); if( isFinal ) m_frame->GetCanvas()->Refresh(); } void APPEARANCE_CONTROLS::onObjectVisibilityChanged( GAL_LAYER_ID aLayer, bool isVisible, bool isFinal ) { // Special-case controls switch( aLayer ) { case LAYER_RATSNEST: { // don't touch the layers. ratsnest is enabled on per-item basis. m_frame->GetCanvas()->GetView()->MarkTargetDirty( KIGFX::TARGET_NONCACHED ); m_frame->GetCanvas()->GetView()->SetLayerVisible( aLayer, true ); if( m_frame->IsType( FRAME_PCB_EDITOR ) ) { PCB_DISPLAY_OPTIONS opt = m_frame->GetDisplayOptions(); opt.m_ShowGlobalRatsnest = isVisible; m_frame->SetDisplayOptions( opt ); m_frame->GetBoard()->SetElementVisibility( aLayer, isVisible ); m_frame->GetCanvas()->RedrawRatsnest(); } break; } case LAYER_GRID: m_frame->SetGridVisibility( isVisible ); m_frame->GetCanvas()->Refresh(); syncLayerPresetSelection(); break; case LAYER_MOD_TEXT_FR: // Because Footprint Text is a meta-control that also can disable values/references, // drag them along here so that the user is less likely to be confused. if( isFinal ) { // Should only trigger when you actually click the Footprint Text button // Otherwise it goes into infinite recursive loop with the following case section onObjectVisibilityChanged( LAYER_MOD_REFERENCES, isVisible, false ); onObjectVisibilityChanged( LAYER_MOD_VALUES, isVisible, false ); m_objectSettingsMap[LAYER_MOD_REFERENCES]->ctl_visibility->SetValue( isVisible ); m_objectSettingsMap[LAYER_MOD_VALUES]->ctl_visibility->SetValue( isVisible ); } break; case LAYER_MOD_REFERENCES: case LAYER_MOD_VALUES: // In case that user changes Footprint Value/References when the Footprint Text // meta-control is disabled, we should put it back on. if( isVisible ) { onObjectVisibilityChanged( LAYER_MOD_TEXT_FR, isVisible, false ); m_objectSettingsMap[LAYER_MOD_TEXT_FR]->ctl_visibility->SetValue( isVisible ); } break; default: break; } GAL_SET visible = getVisibleObjects(); if( visible.Contains( aLayer ) != isVisible ) { visible.set( aLayer, isVisible ); setVisibleObjects( visible ); m_frame->GetCanvas()->GetView()->SetLayerVisible( aLayer, isVisible ); syncLayerPresetSelection(); } if( isFinal ) { m_frame->GetCanvas()->Refresh(); passOnFocus(); } } void APPEARANCE_CONTROLS::rebuildObjects() { COLOR_SETTINGS* theme = m_frame->GetColorSettings(); COLOR4D bgColor = theme->GetColor( LAYER_PCB_BACKGROUND ); GAL_SET visible = getVisibleObjects(); int swatchWidth = m_windowObjects->ConvertDialogToPixels( wxSize( 8, 0 ) ).x; int labelWidth = 0; m_objectSettings.clear(); m_objectsOuterSizer->Clear( true ); m_objectsOuterSizer->AddSpacer( 5 ); auto appendObject = [&]( const std::unique_ptr& aSetting ) { wxBoxSizer* sizer = new wxBoxSizer( wxHORIZONTAL ); int layer = aSetting->id; aSetting->visible = visible.Contains( ToGalLayer( layer ) ); COLOR4D color = theme->GetColor( layer ); COLOR4D defColor = theme->GetDefaultColor( layer ); if( color != COLOR4D::UNSPECIFIED ) { COLOR_SWATCH* swatch = new COLOR_SWATCH( m_windowObjects, color, layer, bgColor, defColor, SWATCH_SMALL ); swatch->SetToolTip( _( "Left double click or middle click for color change, " "right click for menu" ) ); sizer->Add( swatch, 0, wxALIGN_CENTER_VERTICAL, 0 ); aSetting->ctl_color = swatch; swatch->Bind( COLOR_SWATCH_CHANGED, &APPEARANCE_CONTROLS::OnColorSwatchChanged, this ); swatch->SetReadOnlyCallback( std::bind( &APPEARANCE_CONTROLS::onReadOnlySwatch, this ) ); } else { sizer->AddSpacer( swatchWidth ); } BITMAP_TOGGLE* btn_visible = new BITMAP_TOGGLE( m_windowObjects, layer, KiBitmap( BITMAPS::visibility ), KiBitmap( BITMAPS::visibility_off ), aSetting->visible ); wxString tip; tip.Printf( _( "Show or hide %s" ), aSetting->label.Lower() ); btn_visible->SetToolTip( tip ); aSetting->ctl_visibility = btn_visible; sizer->AddSpacer( 5 ); btn_visible->Bind( TOGGLE_CHANGED, [&]( wxCommandEvent& aEvent ) { int id = static_cast( aEvent.GetEventObject() )->GetId(); bool isVisible = aEvent.GetInt(); onObjectVisibilityChanged( ToGalLayer( id ), isVisible, true ); } ); wxStaticText* label = new wxStaticText( m_windowObjects, layer, aSetting->label ); label->Wrap( -1 ); label->SetToolTip( aSetting->tooltip ); if( aSetting->can_control_opacity ) { label->SetMinSize( wxSize( labelWidth, -1 ) ); #ifdef __WXMAC__ sizer->Add( btn_visible, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM, 10 ); sizer->AddSpacer( 5 ); sizer->Add( label, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM, 10 ); #else sizer->Add( btn_visible, 0, wxALIGN_CENTER_VERTICAL, 0 ); sizer->AddSpacer( 5 ); sizer->Add( label, 0, wxALIGN_CENTER_VERTICAL, 0 ); #endif wxSlider* slider = new wxSlider( m_windowObjects, wxID_ANY, 100, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL ); #ifdef __WXMAC__ slider->SetMinSize( wxSize( 80, 16 ) ); #else slider->SetMinSize( wxSize( 80, -1 ) ); #endif tip.Printf( _( "Set opacity of %s" ), aSetting->label.Lower() ); slider->SetToolTip( tip ); sizer->Add( slider, 1, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5 ); aSetting->ctl_opacity = slider; auto opacitySliderHandler = [=]( wxCommandEvent& aEvent ) { wxSlider* ctrl = static_cast( aEvent.GetEventObject() ); int value = ctrl->GetValue(); onObjectOpacitySlider( layer, value / 100.0f ); }; slider->Bind( wxEVT_SCROLL_CHANGED, opacitySliderHandler ); slider->Bind( wxEVT_SCROLL_THUMBTRACK, opacitySliderHandler ); slider->Bind( wxEVT_SET_FOCUS, &APPEARANCE_CONTROLS::OnSetFocus, this ); } else { sizer->Add( btn_visible, 0, wxALIGN_CENTER_VERTICAL, 0 ); sizer->AddSpacer( 5 ); sizer->Add( label, 0, wxALIGN_CENTER_VERTICAL, 0 ); } aSetting->ctl_text = label; m_objectsOuterSizer->Add( sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 5 ); if( !aSetting->can_control_opacity ) m_objectsOuterSizer->AddSpacer( 2 ); }; for( const APPEARANCE_SETTING& s_setting : s_objectSettings ) { if( m_isFpEditor && !s_allowedInFpEditor.count( s_setting.id ) ) continue; if( !s_setting.spacer ) { m_objectSettings.emplace_back( std::make_unique( s_setting ) ); std::unique_ptr& setting = m_objectSettings.back(); // Because s_render_rows is created static, we must explicitly call // wxGetTranslation for texts which are internationalized (tool tips // and item names) setting->tooltip = wxGetTranslation( s_setting.tooltip ); setting->label = wxGetTranslation( s_setting.label ); if( setting->can_control_opacity ) { int width = m_windowObjects->GetTextExtent( setting->label ).x + 5; labelWidth = std::max( labelWidth, width ); } m_objectSettingsMap[ToGalLayer( setting->id )] = setting.get(); } } for( const std::unique_ptr& setting : m_objectSettings ) { if( setting->spacer ) m_objectsOuterSizer->AddSpacer( m_pointSize ); else appendObject( setting ); } m_objectsOuterSizer->Layout(); } void APPEARANCE_CONTROLS::syncObjectSettings() { GAL_SET visible = getVisibleObjects(); const PCB_DISPLAY_OPTIONS& opts = m_frame->GetDisplayOptions(); for( std::unique_ptr& setting : m_objectSettings ) { if( setting->spacer ) continue; GAL_LAYER_ID layer = ToGalLayer( setting->id ); if( setting->ctl_visibility ) setting->ctl_visibility->SetValue( visible.Contains( layer ) ); if( setting->ctl_color ) { COLOR4D color = m_frame->GetColorSettings()->GetColor( setting->id ); setting->ctl_color->SetSwatchColor( color, false ); } } wxASSERT( m_objectSettingsMap.count( LAYER_TRACKS ) && m_objectSettingsMap.count( LAYER_VIAS ) && m_objectSettingsMap.count( LAYER_PADS ) && m_objectSettingsMap.count( LAYER_ZONES ) ); m_objectSettingsMap[LAYER_TRACKS]->ctl_opacity->SetValue( opts.m_TrackOpacity * 100 ); m_objectSettingsMap[LAYER_VIAS]->ctl_opacity->SetValue( opts.m_ViaOpacity * 100 ); m_objectSettingsMap[LAYER_PADS]->ctl_opacity->SetValue( opts.m_PadOpacity * 100 ); m_objectSettingsMap[LAYER_ZONES]->ctl_opacity->SetValue( opts.m_ZoneOpacity * 100 ); } void APPEARANCE_CONTROLS::rebuildNets() { BOARD* board = m_frame->GetBoard(); COLOR_SETTINGS* theme = m_frame->GetColorSettings(); COLOR4D bgColor = theme->GetColor( LAYER_PCB_BACKGROUND ); // If the board isn't fully loaded, we can't yet rebuild if( !board->GetProject() ) return; KIGFX::PCB_RENDER_SETTINGS* rs = static_cast( m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings() ); std::map& netclassColors = rs->GetNetclassColorMap(); m_netclassOuterSizer->Clear( true ); auto appendNetclass = [&]( int aId, const NETCLASSPTR& aClass, bool isDefaultClass = false ) { wxString name = aClass->GetName(); m_netclassSettings.emplace_back( std::make_unique() ); APPEARANCE_SETTING* setting = m_netclassSettings.back().get(); m_netclassSettingsMap[name] = setting; setting->ctl_panel = new wxPanel( m_netclassScrolledWindow, aId ); wxBoxSizer* sizer = new wxBoxSizer( wxHORIZONTAL ); setting->ctl_panel->SetSizer( sizer ); COLOR4D color = netclassColors.count( name ) ? netclassColors.at( name ) : COLOR4D::UNSPECIFIED; setting->ctl_color = new COLOR_SWATCH( setting->ctl_panel, color, aId, bgColor, COLOR4D::UNSPECIFIED, SWATCH_SMALL ); setting->ctl_color->SetToolTip( _( "Left double click or middle click for color " "change, right click for menu" ) ); setting->ctl_color->Bind( COLOR_SWATCH_CHANGED, &APPEARANCE_CONTROLS::onNetclassColorChanged, this ); // Default netclass can't have an override color if( isDefaultClass ) setting->ctl_color->Hide(); setting->ctl_visibility = new BITMAP_TOGGLE( setting->ctl_panel, aId, KiBitmap( BITMAPS::visibility ), KiBitmap( BITMAPS::visibility_off ), true ); wxString tip; tip.Printf( _( "Show or hide ratsnest for nets in %s" ), name ); setting->ctl_visibility->SetToolTip( tip ); setting->ctl_text = new wxStaticText( setting->ctl_panel, aId, name ); setting->ctl_text->Wrap( -1 ); int flags = wxALIGN_CENTER_VERTICAL; sizer->Add( setting->ctl_color, 0, flags | wxRESERVE_SPACE_EVEN_IF_HIDDEN, 5 ); sizer->AddSpacer( 7 ); sizer->Add( setting->ctl_visibility, 0, flags, 5 ); sizer->AddSpacer( 3 ); sizer->Add( setting->ctl_text, 1, flags, 5 ); m_netclassOuterSizer->Add( setting->ctl_panel, 0, wxEXPAND, 5 ); m_netclassOuterSizer->AddSpacer( 2 ); setting->ctl_visibility->Bind( TOGGLE_CHANGED, &APPEARANCE_CONTROLS::onNetclassVisibilityChanged, this ); auto menuHandler = [&, name, isDefaultClass]( wxMouseEvent& aEvent ) { m_contextMenuNetclass = name; wxMenu menu; if( !isDefaultClass) { menu.Append( new wxMenuItem( &menu, ID_SET_NET_COLOR, _( "Set Netclass Color" ), wxEmptyString, wxITEM_NORMAL ) ); } menu.Append( new wxMenuItem( &menu, ID_HIGHLIGHT_NET, wxString::Format( _( "Highlight Nets in %s" ), name ), wxEmptyString, wxITEM_NORMAL ) ); menu.Append( new wxMenuItem( &menu, ID_SELECT_NET, wxString::Format( _( "Select Tracks and Vias in %s" ), name ), wxEmptyString, wxITEM_NORMAL ) ); menu.Append( new wxMenuItem( &menu, ID_DESELECT_NET, wxString::Format( _( "Unselect Tracks and Vias in %s" ), name ), wxEmptyString, wxITEM_NORMAL ) ); menu.AppendSeparator(); menu.Append( new wxMenuItem( &menu, ID_SHOW_ALL_NETS, _( "Show All Netclasses" ), wxEmptyString, wxITEM_NORMAL ) ); menu.Append( new wxMenuItem( &menu, ID_HIDE_OTHER_NETS, _( "Hide All Other Netclasses" ), wxEmptyString, wxITEM_NORMAL ) ); menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &APPEARANCE_CONTROLS::onNetclassContextMenu, this ); PopupMenu( &menu ); }; setting->ctl_panel->Bind( wxEVT_RIGHT_DOWN, menuHandler ); setting->ctl_visibility->Bind( wxEVT_RIGHT_DOWN, menuHandler ); setting->ctl_color->Bind( wxEVT_RIGHT_DOWN, menuHandler ); setting->ctl_text->Bind( wxEVT_RIGHT_DOWN, menuHandler ); }; const NETCLASS_MAP& classes = board->GetDesignSettings().GetNetClasses().NetClasses(); std::vector names; for( const auto& pair : classes ) names.emplace_back( pair.first ); std::sort( names.begin(), names.end() ); m_netclassIdMap.clear(); int idx = wxID_HIGHEST; NETCLASSPTR defaultClass = board->GetDesignSettings().GetNetClasses().GetDefault(); m_netclassIdMap[idx] = defaultClass->GetName(); appendNetclass( idx++, defaultClass, true ); for( const wxString& name : names ) { m_netclassIdMap[idx] = name; appendNetclass( idx++, classes.at( name ) ); } m_netclassOuterSizer->Layout(); m_netsTable->Rebuild(); m_panelNets->GetSizer()->Layout(); } void APPEARANCE_CONTROLS::rebuildLayerPresetsWidget() { m_cbLayerPresets->Clear(); for( std::pair& pair : m_layerPresets ) m_cbLayerPresets->Append( pair.first, static_cast( &pair.second ) ); m_cbLayerPresets->Append( wxT( "-----" ) ); m_cbLayerPresets->Append( _( "Save preset..." ) ); m_cbLayerPresets->Append( _( "Delete preset..." ) ); m_cbLayerPresets->SetSelection( 0 ); // At least the build in presets should always be present wxASSERT( !m_layerPresets.empty() ); // Default preset: all layers m_currentPreset = &m_layerPresets[presetAllLayers.name]; } void APPEARANCE_CONTROLS::syncLayerPresetSelection() { LSET visibleLayers = getVisibleLayers(); GAL_SET visibleObjects = getVisibleObjects(); auto it = std::find_if( m_layerPresets.begin(), m_layerPresets.end(), [&]( const std::pair& aPair ) { return ( aPair.second.layers == visibleLayers && aPair.second.renderLayers == visibleObjects ); } ); if( it != m_layerPresets.end() ) m_cbLayerPresets->SetStringSelection( it->first ); else m_cbLayerPresets->SetSelection( m_cbLayerPresets->GetCount() - 3 ); // separator m_currentPreset = static_cast( m_cbLayerPresets->GetClientData( m_cbLayerPresets->GetSelection() ) ); } void APPEARANCE_CONTROLS::updateLayerPresetSelection( const wxString& aName ) { int idx = m_cbLayerPresets->FindString( aName ); if( idx >= 0 && m_cbLayerPresets->GetSelection() != idx ) { m_cbLayerPresets->SetSelection( idx ); m_currentPreset = static_cast( m_cbLayerPresets->GetClientData( idx ) ); } else if( idx < 0 ) { m_cbLayerPresets->SetSelection( m_cbLayerPresets->GetCount() - 3 ); // separator } } void APPEARANCE_CONTROLS::onLayerPresetChanged( wxCommandEvent& aEvent ) { int count = m_cbLayerPresets->GetCount(); int index = m_cbLayerPresets->GetSelection(); auto resetSelection = [&]() { if( m_currentPreset ) m_cbLayerPresets->SetStringSelection( m_currentPreset->name ); else m_cbLayerPresets->SetSelection( m_cbLayerPresets->GetCount() - 3 ); }; if( index == count - 3 ) { // Separator: reject the selection resetSelection(); return; } else if( index == count - 2 ) { // Save current state to new preset wxString name; if( m_lastSelectedUserPreset ) name = m_lastSelectedUserPreset->name; wxTextEntryDialog dlg( this, _( "Layer preset name:" ), _( "Save Layer Preset" ), name ); if( dlg.ShowModal() != wxID_OK ) { resetSelection(); return; } name = dlg.GetValue(); bool exists = m_layerPresets.count( name ); if( !exists ) { m_layerPresets[name] = LAYER_PRESET( name, getVisibleLayers(), getVisibleObjects(), UNSELECTED_LAYER ); } LAYER_PRESET* preset = &m_layerPresets[name]; m_currentPreset = preset; if( !exists ) { index = m_cbLayerPresets->Insert( name, index - 1, static_cast( preset ) ); } else { index = m_cbLayerPresets->FindString( name ); m_presetMRU.Remove( name ); } m_cbLayerPresets->SetSelection( index ); m_presetMRU.Insert( name, 0 ); return; } else if( index == count - 1 ) { // Delete a preset wxArrayString headers; std::vector items; headers.Add( _( "Presets" ) ); for( std::pair& pair : m_layerPresets ) { if( !pair.second.readOnly ) { wxArrayString item; item.Add( pair.first ); items.emplace_back( item ); } } EDA_LIST_DIALOG dlg( m_frame, _( "Delete Preset" ), headers, items ); dlg.SetListLabel( _( "Select preset:" ) ); if( dlg.ShowModal() == wxID_OK ) { wxString presetName = dlg.GetTextSelection(); int idx = m_cbLayerPresets->FindString( presetName ); if( idx != wxNOT_FOUND ) { m_layerPresets.erase( presetName ); m_cbLayerPresets->Delete( idx ); m_currentPreset = nullptr; m_presetMRU.Remove( presetName ); } } resetSelection(); return; } LAYER_PRESET* preset = static_cast( m_cbLayerPresets->GetClientData( index ) ); m_currentPreset = preset; m_lastSelectedUserPreset = ( !preset || preset->readOnly ) ? nullptr : preset; if( preset ) doApplyLayerPreset( *preset ); if( !m_currentPreset->name.IsEmpty() ) { m_presetMRU.Remove( m_currentPreset->name ); m_presetMRU.Insert( m_currentPreset->name, 0 ); } passOnFocus(); } void APPEARANCE_CONTROLS::doApplyLayerPreset( const LAYER_PRESET& aPreset ) { BOARD* board = m_frame->GetBoard(); setVisibleLayers( aPreset.layers ); setVisibleObjects( aPreset.renderLayers ); // If the preset doesn't have an explicit active layer to restore, we can at least // force the active layer to be something in the preset's layer set PCB_LAYER_ID activeLayer = UNSELECTED_LAYER; if( aPreset.activeLayer != UNSELECTED_LAYER ) activeLayer = aPreset.activeLayer; else if( aPreset.layers.any() && !aPreset.layers.test( m_frame->GetActiveLayer() ) ) activeLayer = *aPreset.layers.Seq().begin(); LSET boardLayers = board->GetLayerSet(); if( activeLayer != UNSELECTED_LAYER && boardLayers.Contains( activeLayer ) ) m_frame->SetActiveLayer( activeLayer ); if( !m_isFpEditor ) m_frame->GetCanvas()->SyncLayersVisibility( board ); m_frame->GetCanvas()->Refresh(); syncColorsAndVisibility(); } void APPEARANCE_CONTROLS::OnColorSwatchChanged( wxCommandEvent& aEvent ) { COLOR_SWATCH* swatch = static_cast( aEvent.GetEventObject() ); COLOR4D newColor = swatch->GetSwatchColor(); LAYER_NUM layer = swatch->GetId(); COLOR_SETTINGS* cs = m_frame->GetColorSettings(); cs->SetColor( layer, newColor ); m_frame->GetCanvas()->UpdateColors(); KIGFX::VIEW* view = m_frame->GetCanvas()->GetView(); view->UpdateLayerColor( layer ); view->UpdateLayerColor( GetNetnameLayer( layer ) ); if( IsCopperLayer( layer ) ) view->UpdateLayerColor( ZONE_LAYER_FOR( layer ) ); // Update the bitmap of the layer box if( m_frame->IsType( FRAME_PCB_EDITOR ) ) static_cast( m_frame )->ReCreateLayerBox( false ); m_frame->GetCanvas()->Refresh(); if( layer == LAYER_PCB_BACKGROUND ) m_frame->SetDrawBgColor( newColor ); passOnFocus(); } void APPEARANCE_CONTROLS::onObjectOpacitySlider( int aLayer, float aOpacity ) { PCB_DISPLAY_OPTIONS options = m_frame->GetDisplayOptions(); switch( aLayer ) { case static_cast( LAYER_TRACKS ): options.m_TrackOpacity = aOpacity; break; case static_cast( LAYER_VIAS ): options.m_ViaOpacity = aOpacity; break; case static_cast( LAYER_PADS ): options.m_PadOpacity = aOpacity; break; case static_cast( LAYER_ZONES ): options.m_ZoneOpacity = aOpacity; break; default: return; } m_frame->SetDisplayOptions( options ); passOnFocus(); } void APPEARANCE_CONTROLS::onNetContextMenu( wxCommandEvent& aEvent ) { wxASSERT( m_netsGrid->GetSelectedRows().size() == 1 ); int row = m_netsGrid->GetSelectedRows()[0]; NET_GRID_ENTRY& net = m_netsTable->GetEntry( row ); m_netsGrid->ClearSelection(); switch( aEvent.GetId() ) { case ID_SET_NET_COLOR: { wxGridCellEditor* editor = m_netsGrid->GetCellEditor( row, NET_GRID_TABLE::COL_COLOR ); editor->BeginEdit( row, NET_GRID_TABLE::COL_COLOR, m_netsGrid ); break; } case ID_HIGHLIGHT_NET: { m_frame->GetToolManager()->RunAction( PCB_ACTIONS::highlightNet, true, static_cast( net.code ) ); m_frame->GetCanvas()->Refresh(); break; } case ID_SELECT_NET: { m_frame->GetToolManager()->RunAction( PCB_ACTIONS::selectNet, true, static_cast( net.code ) ); m_frame->GetCanvas()->Refresh(); break; } case ID_DESELECT_NET: { m_frame->GetToolManager()->RunAction( PCB_ACTIONS::deselectNet, true, static_cast( net.code ) ); m_frame->GetCanvas()->Refresh(); break; } case ID_SHOW_ALL_NETS: m_netsTable->ShowAllNets(); break; case ID_HIDE_OTHER_NETS: m_netsTable->HideOtherNets( net ); break; default: break; } passOnFocus(); } void APPEARANCE_CONTROLS::onNetclassVisibilityChanged( wxCommandEvent& aEvent ) { wxString className = netclassNameFromEvent( aEvent ); bool show = aEvent.GetInt(); showNetclass( className, show ); passOnFocus(); } void APPEARANCE_CONTROLS::showNetclass( const wxString& aClassName, bool aShow ) { BOARD* board = m_frame->GetBoard(); NETINFO_LIST& nets = board->GetNetInfo(); NETCLASSES& classes = board->GetDesignSettings().GetNetClasses(); NETCLASSPTR netclass = classes.Find( aClassName ); TOOL_MANAGER* manager = m_frame->GetToolManager(); if( !netclass ) return; NETCLASS* defaultClass = classes.GetDefaultPtr(); if( netclass == classes.GetDefault() ) { const TOOL_ACTION& action = aShow ? PCB_ACTIONS::showNet : PCB_ACTIONS::hideNet; for( NETINFO_ITEM* net : nets ) { if( net->GetNetClass() == defaultClass ) { manager->RunAction( action, true, static_cast( net->GetNetCode() ) ); int row = m_netsTable->GetRowByNetcode( net->GetNetCode() ); if( row >= 0 ) m_netsTable->SetValueAsBool( row, NET_GRID_TABLE::COL_VISIBILITY, aShow ); } } } else { const TOOL_ACTION& action = aShow ? PCB_ACTIONS::showNet : PCB_ACTIONS::hideNet; for( const wxString& member : *netclass ) { if( NETINFO_ITEM* net = nets.GetNetItem( member ) ) { int code = net->GetNetCode(); manager->RunAction( action, true, static_cast( code ) ); int row = m_netsTable->GetRowByNetcode( code ); if( row >= 0 ) m_netsTable->SetValueAsBool( row, NET_GRID_TABLE::COL_VISIBILITY, aShow ); } } } m_netsGrid->ForceRefresh(); } void APPEARANCE_CONTROLS::onNetclassColorChanged( wxCommandEvent& aEvent ) { KIGFX::PCB_RENDER_SETTINGS* rs = static_cast( m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings() ); std::map& netclassColors = rs->GetNetclassColorMap(); COLOR_SWATCH* swatch = static_cast( aEvent.GetEventObject() ); wxString className = netclassNameFromEvent( aEvent ); netclassColors[className] = swatch->GetSwatchColor(); m_frame->GetCanvas()->GetView()->UpdateAllLayersColor(); m_frame->GetCanvas()->RedrawRatsnest(); m_frame->GetCanvas()->Refresh(); } wxString APPEARANCE_CONTROLS::netclassNameFromEvent( wxEvent& aEvent ) { COLOR_SWATCH* s = static_cast( aEvent.GetEventObject() ); int classId = s->GetId(); wxASSERT( m_netclassIdMap.count( classId ) ); return m_netclassIdMap.at( classId ); } void APPEARANCE_CONTROLS::onNetColorMode( wxCommandEvent& aEvent ) { PCB_DISPLAY_OPTIONS options = m_frame->GetDisplayOptions(); if( m_rbNetColorAll->GetValue() ) options.m_NetColorMode = NET_COLOR_MODE::ALL; else if( m_rbNetColorRatsnest->GetValue() ) options.m_NetColorMode = NET_COLOR_MODE::RATSNEST; else options.m_NetColorMode = NET_COLOR_MODE::OFF; m_frame->SetDisplayOptions( options ); m_frame->GetCanvas()->GetView()->UpdateAllLayersColor(); passOnFocus(); } void APPEARANCE_CONTROLS::onRatsnestMode( wxCommandEvent& aEvent ) { PCB_DISPLAY_OPTIONS options = m_frame->GetDisplayOptions(); if( m_rbRatsnestAllLayers->GetValue() ) { options.m_ShowGlobalRatsnest = true; options.m_RatsnestMode = RATSNEST_MODE::ALL; } else if( m_rbRatsnestVisLayers->GetValue() ) { options.m_ShowGlobalRatsnest = true; options.m_RatsnestMode = RATSNEST_MODE::VISIBLE; } else { options.m_ShowGlobalRatsnest = false; } m_frame->SetDisplayOptions( options ); m_frame->GetCanvas()->RedrawRatsnest(); passOnFocus(); } void APPEARANCE_CONTROLS::onNetclassContextMenu( wxCommandEvent& aEvent ) { KIGFX::VIEW* view = m_frame->GetCanvas()->GetView(); KIGFX::PCB_RENDER_SETTINGS* rs = static_cast( view->GetPainter()->GetSettings() ); BOARD* board = m_frame->GetBoard(); NETINFO_LIST& nets = board->GetNetInfo(); NETCLASSES& classes = board->GetDesignSettings().GetNetClasses(); NETCLASSPTR netclass = classes.Find( m_contextMenuNetclass ); APPEARANCE_SETTING* setting = m_netclassSettingsMap.count( m_contextMenuNetclass ) ? m_netclassSettingsMap.at( m_contextMenuNetclass ) : nullptr; NETCLASSPTR defaultClass = classes.GetDefault(); wxString defaultClassName = defaultClass->GetName(); auto runOnNetsOfClass = [&]( NETCLASSPTR aClass, std::function aFunction ) { if( aClass == defaultClass ) { for( NETINFO_ITEM* net : nets ) if( net->GetNetClass() == defaultClass.get() ) aFunction( net ); } else { for( const wxString& netName : *aClass ) aFunction( nets.GetNetItem( netName ) ); } }; switch( aEvent.GetId() ) { case ID_SET_NET_COLOR: { if( setting ) { setting->ctl_color->GetNewSwatchColor(); COLOR4D color = setting->ctl_color->GetSwatchColor(); std::map& netclassColors = rs->GetNetclassColorMap(); if( color != COLOR4D::UNSPECIFIED ) netclassColors[m_contextMenuNetclass] = color; else netclassColors.erase( m_contextMenuNetclass ); view->UpdateAllLayersColor(); } break; } case ID_HIGHLIGHT_NET: { if( netclass ) { runOnNetsOfClass( netclass, [&]( NETINFO_ITEM* aItem ) { if( !aItem ) return; static bool first = true; int code = aItem->GetNetCode(); if( first ) { board->SetHighLightNet( code ); rs->SetHighlight( true, code ); first = false; } else { board->SetHighLightNet( code, true ); rs->SetHighlight( true, code, true ); } } ); view->UpdateAllLayersColor(); board->HighLightON(); } break; } case ID_SELECT_NET: case ID_DESELECT_NET: { if( netclass ) { TOOL_ACTION& action = aEvent.GetId() == ID_SELECT_NET ? PCB_ACTIONS::selectNet : PCB_ACTIONS::deselectNet; runOnNetsOfClass( netclass, [&]( NETINFO_ITEM* aItem ) { if( !aItem ) return; intptr_t code = static_cast( aItem->GetNetCode() ); m_frame->GetToolManager()->RunAction( action, true, code ); } ); } break; } case ID_SHOW_ALL_NETS: { showNetclass( defaultClassName ); wxASSERT( m_netclassSettingsMap.count( defaultClassName ) ); m_netclassSettingsMap.at( defaultClassName )->ctl_visibility->SetValue( true ); for( const auto& pair : classes.NetClasses() ) { showNetclass( pair.first ); if( m_netclassSettingsMap.count( pair.first ) ) m_netclassSettingsMap.at( pair.first )->ctl_visibility->SetValue( true ); } break; } case ID_HIDE_OTHER_NETS: { bool showDefault = m_contextMenuNetclass == defaultClassName; showNetclass( defaultClassName, showDefault ); wxASSERT( m_netclassSettingsMap.count( defaultClassName ) ); m_netclassSettingsMap.at( defaultClassName )->ctl_visibility->SetValue( showDefault ); for( const auto& pair : classes.NetClasses() ) { bool show = pair.first == m_contextMenuNetclass; showNetclass( pair.first, show ); if( m_netclassSettingsMap.count( pair.first ) ) m_netclassSettingsMap.at( pair.first )->ctl_visibility->SetValue( show ); } break; } default: break; } m_frame->GetCanvas()->RedrawRatsnest(); m_frame->GetCanvas()->Refresh(); m_contextMenuNetclass.clear(); } void APPEARANCE_CONTROLS::passOnFocus() { m_focusOwner->SetFocus(); } void APPEARANCE_CONTROLS::onReadOnlySwatch() { WX_INFOBAR* infobar = m_frame->GetInfoBar(); wxHyperlinkCtrl* button = new wxHyperlinkCtrl( infobar, wxID_ANY, _( "Open Preferences" ), wxEmptyString ); button->Bind( wxEVT_COMMAND_HYPERLINK, std::function( [&]( wxHyperlinkEvent& aEvent ) { wxCommandEvent dummy; m_frame->OnPreferences( dummy ); } ) ); infobar->RemoveAllButtons(); infobar->AddButton( button ); infobar->AddCloseButton(); infobar->ShowMessageFor( _( "The current color theme is read-only. Create a new theme in " "Preferences to enable color editing." ), 10000, wxICON_INFORMATION ); } void APPEARANCE_CONTROLS::RefreshCollapsiblePanes() { m_paneLayerDisplayOptions->Refresh(); }