39#include <wx/clipbrd.h>
40#include <wx/dataobj.h>
41#include <wx/settings.h>
42#include <wx/stattext.h>
43#include <wx/propgrid/advprops.h>
49#if !wxCHECK_VERSION( 3, 3, 0 )
58 wxPropertyGrid( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_DEFAULT_STYLE | wxPG_TOOLTIPS )
62 void ScrollWindow(
int aDx,
int aDy,
const wxRect* aRect =
nullptr )
override
64 wxPropertyGrid::ScrollWindow( aDx, aDy, aRect );
67 panel->positionCategoryButtons();
74 wxStatusBar* GetStatusBar()
override {
return nullptr; }
86 wxBoxSizer* mainSizer =
new wxBoxSizer( wxVERTICAL );
88#if !wxCHECK_VERSION( 3, 3, 0 )
92 wxPGInitResourceModule();
99 wxPGEditor_TextCtrl =
nullptr;
100 wxPGEditor_Choice =
nullptr;
101 wxPGEditor_ComboBox =
nullptr;
102 wxPGEditor_TextCtrlAndButton =
nullptr;
103 wxPGEditor_CheckBox =
nullptr;
104 wxPGEditor_ChoiceAndButton =
nullptr;
105 wxPGEditor_SpinCtrl =
nullptr;
106 wxPGEditor_DatePickerCtrl =
nullptr;
109 if( !
Pgm().m_PropertyGridInitialized )
116 m_caption =
new wxStaticText(
this, wxID_ANY,
_(
"No objects selected" ) );
117 mainSizer->Add(
m_caption, 0, wxALL | wxEXPAND, 5 );
120 m_grid->SetUnspecifiedValueAppearance( wxPGCell( wxT(
"<...>" ) ) );
122#if wxCHECK_VERSION( 3, 3, 0 )
123 m_grid->SetValidationFailureBehavior( wxPGVFBFlags::MarkCell );
125 m_grid->SetValidationFailureBehavior( wxPG_VFB_MARK_CELL );
128#if wxCHECK_VERSION( 3, 3, 0 )
129 m_grid->AddActionTrigger( wxPGKeyboardAction::NextProperty, WXK_RETURN );
130 m_grid->AddActionTrigger( wxPGKeyboardAction::NextProperty, WXK_NUMPAD_ENTER );
131 m_grid->AddActionTrigger( wxPGKeyboardAction::NextProperty, WXK_DOWN );
132 m_grid->AddActionTrigger( wxPGKeyboardAction::PrevProperty, WXK_UP );
133 m_grid->AddActionTrigger( wxPGKeyboardAction::Edit, WXK_SPACE );
135 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_RETURN );
136 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_NUMPAD_ENTER );
137 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_DOWN );
138 m_grid->AddActionTrigger( wxPG_ACTION_PREV_PROPERTY, WXK_UP );
139 m_grid->AddActionTrigger( wxPG_ACTION_EDIT, WXK_SPACE );
142 m_grid->DedicateKey( WXK_RETURN );
143 m_grid->DedicateKey( WXK_NUMPAD_ENTER );
144 m_grid->DedicateKey( WXK_DOWN );
145 m_grid->DedicateKey( WXK_UP );
146 mainSizer->Add(
m_grid, 1, wxEXPAND, 5 );
148 m_grid->SetCellDisabledTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
152 m_grid->SetCaptionTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_CAPTIONTEXT ) );
157 SetSizer( mainSizer );
164 m_grid->MakeColumnEditable( 0 );
166 Bind( wxEVT_PG_ITEM_EXPANDED,
167 [&]( wxPropertyGridEvent& )
172 Bind( wxEVT_PG_ITEM_COLLAPSED,
173 [&]( wxPropertyGridEvent& )
187 Bind( wxEVT_PG_COL_END_DRAG,
188 [&]( wxPropertyGridEvent& )
195 [&]( wxSizeEvent& aEvent )
208 [
this]( wxTimerEvent& )
224 if(
m_grid->IsEditorFocused() )
225 m_grid->CommitChangesFromEditor();
239 m_panel->m_SuppressGridChangeEvents++;
245 m_panel->m_SuppressGridChangeEvents--;
269 if(
m_grid->IsEditorFocused() )
270 m_grid->CommitChangesFromEditor();
276 if( aSelection.
Empty() )
278 m_caption->SetLabel(
_(
"No objects selected" ) );
283 else if( aSelection.
Size() == 1 )
289 m_caption->SetLabel( wxString::Format(
_(
"%d objects selected" ), aSelection.
Size() ) );
293 std::set<TYPE_ID> types;
298 wxCHECK( !types.empty(), );
301 std::map<wxString, PROPERTY_BASE*> commonProps;
302 const std::vector<PROPERTY_BASE*>& allProperties = propMgr.
GetProperties( *types.begin() );
305 commonProps.emplace( property->Name(), property );
307 std::map<wxString, int> displayOrder;
309 displayOrder.emplace( entry.first->Name(), entry.second );
312 std::set<wxString> groups( groupDisplayOrder.begin(), groupDisplayOrder.end() );
315 for(
auto itType = std::next( types.begin() ); itType != types.end(); ++itType )
321 if( !groups.count(
group ) )
323 groupDisplayOrder.emplace_back(
group );
324 groups.insert(
group );
328 for(
auto it = commonProps.begin(); it != commonProps.end(); )
333 if( it->second->IgnoreValue() )
340 it = commonProps.erase( it );
345 for(
const EDA_ITEM* item : aSelection )
349 bool commonToAll =
true;
351 for(
const EDA_ITEM* other : aSelection )
353 std::vector<PROPERTY_BASE*> otherProps = other->GetDynamicProperties();
355 if( std::ranges::none_of( otherProps,
358 return p->
Name() == prop->Name();
366 if( !commonToAll || commonProps.contains( prop->Name() ) )
369 commonProps.emplace( prop->Name(), prop );
371 auto maxOrderIt = std::ranges::max_element( displayOrder,
372 [](
const auto& aL,
const auto& aR )
374 return aL.second < aR.second;
376 int nextOrder = maxOrderIt == displayOrder.end() ? 0 : maxOrderIt->second + 1;
377 displayOrder.emplace( prop->Name(), nextOrder );
379 if(
const wxString& dynGroup = prop->Group(); !dynGroup.IsEmpty() && !groups.contains( dynGroup ) )
381 groupDisplayOrder.emplace_back( dynGroup );
382 groups.insert( dynGroup );
389 if( !groups.contains(
_HKI(
"Custom Properties" ) ) )
391 groupDisplayOrder.emplace_back(
_HKI(
"Custom Properties" ) );
392 groups.insert(
_HKI(
"Custom Properties" ) );
399 if( !entry.forceCategory || groups.contains( entry.groupKey ) )
402 if( entry.enableFunc && !entry.enableFunc() )
405 groupDisplayOrder.emplace_back( entry.groupKey );
406 groups.insert( entry.groupKey );
415 std::set<wxString> availableProps;
418 for(
auto& [
name, property] : commonProps )
420 if( property->IsHiddenFromPropertiesManager() )
423 if( isLibraryEditor && property->IsHiddenFromLibraryEditors() )
426 if( isDesignEditor && property->IsHiddenFromDesignEditors() )
434 availableProps.insert(
name );
437 bool writeable =
true;
438 std::set<wxString> existingProps;
440 for( wxPropertyGridIterator it =
m_grid->GetIterator(); !it.AtEnd(); it.Next() )
442 wxPGProperty* pgProp = it.GetProperty();
443 wxString
name = pgProp->GetName();
447 existingProps.insert(
name );
449 if( !availableProps.count(
name ) )
457 if( choices.GetCount() > 0 )
458 pgProp->SetChoices( choices );
460 pgProp->SetValue( commonVal );
461 pgProp->ChangeFlag( wxPG_PROP_READONLY, !writeable );
464 if( !existingProps.empty() && existingProps == availableProps )
470 std::map<wxPGProperty*, int> pgPropOrders;
471 std::map<wxString, std::vector<wxPGProperty*>> pgPropGroups;
473 for(
const wxString&
name : availableProps )
485 if( choices.GetCount() )
486 pgProp->SetChoices( choices );
488 pgProp->SetValue( commonVal );
489 pgProp->ChangeFlag( wxPG_PROP_READONLY, !writeable );
492 wxASSERT( displayOrder.count(
name ) );
493 pgPropOrders[pgProp] = displayOrder[
name];
494 pgPropGroups[
property->Group()].emplace_back( pgProp );
498 const wxString unspecifiedGroupCaption =
_(
"Basic Properties" );
500 for(
const wxString& groupName : groupDisplayOrder )
502 if( groupName !=
_HKI(
"Custom Properties" ) && !pgPropGroups.contains( groupName ) )
505 std::vector<wxPGProperty*> properties;
507 if( pgPropGroups.contains( groupName ) )
508 properties = pgPropGroups[groupName];
510 wxString groupCaption = wxGetTranslation( groupName );
512 auto groupItem =
new wxPropertyCategory( groupName.IsEmpty() ? unspecifiedGroupCaption
515 m_grid->Append( groupItem );
517 std::sort( properties.begin(), properties.end(),
518 [&]( wxPGProperty*& aFirst, wxPGProperty*& aSecond )
520 return pgPropOrders[aFirst] < pgPropOrders[aSecond];
523 for( wxPGProperty* property : properties )
524 m_grid->Append( property );
534 const wxAny&
any = aItem->
Get( aProperty );
535 bool converted =
false;
541 converted =
any.GetAs<
int>( &tmp );
544 aValue = wxVariant( tmp );
548 converted =
any.GetAs( &aValue );
552 wxString propName = aProperty->
Name();
553 propName.Replace(
' ',
'_' );
554 wxFAIL_MSG( wxString::Format( wxS(
"Could not convert wxAny to wxVariant for %s::%s" ),
564 wxVariant& aValue,
bool& aWritable, wxPGChoices& aChoices )
567 bool different =
false;
582 if( property->IsHiddenFromPropertiesManager() )
585 if( property->IgnoreValue() )
588 wxPGChoices choices =
property->
GetChoices( item );
597 wxArrayString labels = choices.GetLabels();
598 wxArrayInt values = choices.GetValuesForStrings( labels );
600 if( labels != aChoices.GetLabels() || values != aChoices.GetValuesForStrings( labels ) )
613 if( !different && !aValue.IsNull() && value != aValue )
618 else if( !different )
636 if( aEvent.IsShown() )
645 wxPGProperty* pgProp = aEvent.GetProperty();
660 wxPGProperty* pgProp = aEvent.GetProperty();
665 const wxString oldName = pgProp->GetBaseName();
667 wxTextCtrl* labelEditor =
m_grid->GetLabelEditor();
671 newName = labelEditor->GetValue();
697 [
this, oldName, newName]()
706 if( newName == oldName )
714 [
this, oldName, originalLabel, newName]()
716 for( wxPropertyGridIterator it =
m_grid->GetIterator(); !it.AtEnd(); it.Next() )
718 wxPGProperty* p = it.GetProperty();
720 if( p->GetBaseName() == oldName && p->GetLabel() == newName )
722 p->SetLabel( originalLabel );
733 [
this, oldName, newName]()
742 wxPGProperty* pgProp = aEvent.GetProperty();
749 wxMenu* menu =
new wxMenu;
758 const wxPoint pos = ScreenToClient( wxGetMousePosition() );
759 CallAfter( [
this, menu, pos]()
761 PopupMenu( menu, pos );
769 if( !
m_grid->GetLabelEditor() )
775 const wxString newName =
m_grid->GetLabelEditor()->GetValue();
779 m_grid->EndLabelEdit(
true );
782 if( pendingKey.IsEmpty() )
787 else if( newName != pendingKey )
794 for( wxPropertyGridIterator it =
m_grid->GetIterator(); !it.AtEnd(); it.Next() )
796 wxPGProperty* p = it.GetProperty();
798 if( !p->IsCategory() && p->GetBaseName() == aKey )
800 m_grid->SelectProperty( p );
801 m_grid->BeginLabelEdit( 0 );
803 if( aStartBlank &&
m_grid->GetLabelEditor() )
804 m_grid->GetLabelEditor()->SetValue( wxEmptyString );
816 if( aEvent.GetKeyCode() == WXK_TAB
817 && ( aEvent.GetModifiers() == wxMOD_NONE || aEvent.GetModifiers() == wxMOD_SHIFT ) )
823 m_grid->CommitChangesFromEditor();
825 const bool forward = !aEvent.ShiftDown();
828 []( wxPGProperty* aProp )
830 return aProp && !aProp->IsCategory() && aProp->IsVisible() && aProp->IsEnabled()
831 && !aProp->HasFlag( wxPG_PROP_READONLY ) && aProp->GetEditorClass();
835 [&]( wxPropertyGridIterator aIt )
837 while( !aIt.AtEnd() )
839 if( isTabStop( aIt.GetProperty() ) )
840 return aIt.GetProperty();
848 return static_cast<wxPGProperty*
>( nullptr );
851 wxPGProperty* current =
m_grid->GetSelectedProperty();
852 wxPGProperty* target =
nullptr;
856 wxPropertyGridIterator it =
m_grid->GetIterator( wxPG_ITERATE_VISIBLE, current );
863 target = findTarget( it );
869 wxPropertyGridIterator it =
m_grid->GetIterator( wxPG_ITERATE_VISIBLE,
870 forward ? wxTOP : wxBOTTOM );
871 target = findTarget( it );
875 m_grid->SelectProperty( target,
true );
882 if( aEvent.GetKeyCode() ==
'C' && aEvent.GetModifiers() == wxMOD_CONTROL )
884 if( wxPGProperty* prop =
m_grid->GetSelectedProperty() )
886 if( wxTheClipboard->Open() )
888 wxTheClipboard->SetData(
new wxTextDataObject( prop->GetValueAsString() ) );
889 wxTheClipboard->Close();
895 if( aEvent.GetKeyCode() == WXK_SPACE )
897 if( wxPGProperty* prop =
m_grid->GetSelectedProperty() )
899 if( prop->GetValueType() == wxT(
"bool" ) )
901 m_grid->SetPropertyValue( prop, !prop->GetValue().GetBool() );
907 if( aEvent.GetKeyCode() == WXK_RETURN || aEvent.GetKeyCode() == WXK_NUMPAD_ENTER
908 || aEvent.GetKeyCode() == WXK_DOWN || aEvent.GetKeyCode() == WXK_UP )
910 m_grid->CommitChangesFromEditor();
914 m_grid->SelectProperty(
m_grid->GetSelectedProperty(),
true );
939 std::function<
void()> aAction, std::function<
bool()> aEnableFunc,
940 bool aForceCategory )
944 button->SetToolTip( aTooltip );
947 button->Bind( wxEVT_BUTTON,
948 [
this, button]( wxCommandEvent& )
952 if( entry.button == button )
961 .groupKey = aGroupKey,
962 .action = std::move( aAction ),
963 .enableFunc = std::move( aEnableFunc ),
964 .forceCategory = aForceCategory } );
970 const wxString caption = wxGetTranslation( aGroupKey );
972 for( wxPropertyGridIterator it =
m_grid->GetIterator( wxPG_ITERATE_VISIBLE ); !it.AtEnd(); it.Next() )
974 if( wxPGProperty* pgProp = it.GetProperty(); pgProp->IsCategory() && pgProp->GetLabel() == caption )
991 entry.button->Hide();
997 const int rightEdgeBase =
m_grid->GetClientSize().x -
m_grid->FromDIP( 4 );
998 int nextRightEdge = rightEdgeBase;
1000 bool haveCurrentRow =
false;
1004 if(
bool sameRow = haveCurrentRow && currentRow == entry.groupKey; !sameRow )
1006 currentRow = entry.groupKey;
1007 haveCurrentRow =
true;
1008 nextRightEdge = rightEdgeBase;
1012 bool visible =
false;
1014 int rowHeight =
m_grid->GetRowHeight();
1016 if( category && ( !entry.enableFunc || entry.enableFunc() ) )
1018 rowY =
m_grid->CalcScrolledPosition( wxPoint( 0, category->GetY() ) ).y;
1019 visible = ( rowY + rowHeight > 0 ) && ( rowY < m_grid->GetClientSize().y );
1024 entry.button->Hide();
1028 const wxSize btnSize = entry.button->GetSize();
1029 const int btnY = rowY + ( rowHeight - btnSize.y ) / 2;
1030 const int btnX = nextRightEdge - btnSize.x;
1032 if( !entry.button->IsShown() )
1033 entry.button->Show();
1035 entry.button->SetPosition( wxPoint( std::max( 0, btnX ), std::max( 0, btnY ) ) );
1036 entry.button->Raise();
1038 nextRightEdge = btnX -
m_grid->FromDIP( 2 );
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
BITMAPS
A list of all bitmap identifiers.
The base frame for deriving all KiCad main window classes.
A base class for most all the KiCad significant classes used in schematics and boards.
virtual wxString GetFriendlyName() const
wxAny Get(PROPERTY_BASE *aProperty) const
virtual wxString GetClass() const =0
Return the class name.
bool m_PropertyGridInitialized
Enhanced renderer to work around some limitations in wxWidgets 3.0 capabilities.
bool IsProcessingWxPGEvent() const
True while a wxPropertyGrid event (e.g. right-click) is being processed.
void ScrollWindow(int aDx, int aDy, const wxRect *aRect=nullptr) override
PROPERTIES_PANEL_GRID(wxWindow *aParent)
wxString m_contextMenuPropertyName
virtual ~PROPERTIES_PANEL()
float m_splitter_key_proportion
Proportion of the grid column splitter that is used for the key column (0.0 - 1.0)
virtual bool isKeyEditable(const wxPGProperty *aPGProp) const
virtual void onKeyRenamed(const wxString &aOldName, const wxString &aNewName)
PROPERTIES_PANEL(wxWindow *aParent, EDA_BASE_FRAME *aFrame)
virtual void valueChanging(wxPropertyGridEvent &aEvent)
wxString m_editingOriginalLabel
virtual void onLabelEditEnding(wxPropertyGridEvent &aEvent)
void RecalculateSplitterPos()
virtual bool buildContextMenu(wxMenu &aMenu, wxPGProperty *aPGProp)
void beginLabelEdit(const wxString &aKey, bool aStartBlank=false)
virtual bool isKeyNameInUse(const wxString &aName) const
bool m_resolvingPendingKey
True while settling a pending label edit synchronously (see settlePendingLabelEdit).
void SetSplitterProportion(float aProportion)
void onRightClick(wxPropertyGridEvent &aEvent)
void settlePendingLabelEdit()
Synchronously ends any in-progress label edit; potentially canceling a newly added row.
std::vector< PROPERTY_BASE * > m_displayed
int m_SuppressGridChangeEvents
virtual void OnLanguageChanged(wxCommandEvent &aEvent)
virtual void UpdateData()=0
friend class PROPERTIES_PANEL_GRID
virtual void onLabelEditBegin(wxPropertyGridEvent &aEvent)
virtual void valueChanged(wxPropertyGridEvent &aEvent)
virtual void onNewItemLeftBlank(const wxString &aKey)
bool extractValueAndWritability(const SELECTION &aSelection, const wxString &aPropName, wxVariant &aValue, bool &aWritable, wxPGChoices &aChoices)
Processes a selection and determines whether the given property should be available or not and what t...
wxTimer m_deferredRebuildTimer
Not CallAfter(): on macOS the pending-event queue is drained from inside tool events,...
SELECTION m_deferredSelection
virtual bool getItemValue(EDA_ITEM *aItem, PROPERTY_BASE *aProperty, wxVariant &aValue)
Utility to fetch a property value and convert to wxVariant Precondition: aItem is known to have prope...
wxPGProperty * categoryForGroup(const wxString &aGroupKey) const
Find the caption row for the given untranslated group name, if present.
virtual void rebuildProperties(const SELECTION &aSelection)
Generates the property grid for a given selection of items.
void onCharHook(wxKeyEvent &aEvent)
virtual wxPGProperty * createPGProperty(const PROPERTY_BASE *aProperty) const =0
void addCategoryButton(const wxString &aGroupKey, const wxString &aTooltip, BITMAPS aBitmap, std::function< void()> aAction, std::function< bool()> aEnableFunc=nullptr, bool aForceCategory=false)
Registers an overlay button on a category caption row.
void onShow(wxShowEvent &aEvent)
void hideCategoryButtons()
Hides all overlay buttons (used when no selection or a rebuild is deferred).
std::vector< CATEGORY_BUTTON > m_categoryButtons
void positionCategoryButtons()
void updateCategoryButtons()
Updates overlay button visibility/positions; called after rebuilds and on scroll.
wxString m_pendingNewKey
Key of a freshly-added blank field/custom property awaiting a name from the user.
virtual bool HasChoices() const
Return true if this PROPERTY has a limited set of possible values.
wxPGChoices GetChoices(INSPECTABLE *aObject) const
const wxString & Name() const
const std::vector< PROPERTY_BASE * > & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
bool IsWriteableFor(TYPE_ID aItemClass, PROPERTY_BASE *aProp, INSPECTABLE *aItem)
Checks overriden availability and original availability of a property, returns false if the property ...
static PROPERTY_MANAGER & Instance()
PROPERTY_BASE * GetProperty(TYPE_ID aType, const wxString &aProperty) const
Return a property for a specific type.
bool IsAvailableFor(TYPE_ID aItemClass, PROPERTY_BASE *aProp, INSPECTABLE *aItem)
Checks overriden availability and original availability of a property, returns false if the property ...
const std::map< PROPERTY_BASE *, int > & GetDisplayOrder(TYPE_ID aType) const
const std::vector< wxString > & GetGroupDisplayOrder(TYPE_ID aType) const
int Size() const
Returns the number of selected parts.
bool Empty() const
Checks if there is anything selected.
SUPPRESS_GRID_CHANGED_EVENTS(PROPERTIES_PANEL *aPanel)
~SUPPRESS_GRID_CHANGED_EVENTS()
PROPERTIES_PANEL * m_panel
A type-safe container of any type.
Base window classes and related definitions.
@ FRAME_SCH_SYMBOL_EDITOR
Some functions to handle hotkeys in KiCad.
KICOMMON_API wxFont GetDockedPaneFont(wxWindow *aWindow)
PGM_BASE & Pgm()
The global program "get" accessor.
APIIMPORT wxPGGlobalVarsClass * wxPGGlobalVars
#define TYPE_HASH(x)
Macro to generate unique identifier for a type.
size_t TYPE_ID
Unique type identifier.
std::vector< FAB_LAYER_COLOR > dummy