KiCad PCB EDA Suite
Loading...
Searching...
No Matches
properties_panel.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KICAD, a free EDA CAD application.
3 *
4 * Copyright (C) 2020 CERN
5 * Copyright (C) 2020-2022 KiCad Developers, see AUTHORS.txt for contributors.
6 * @author Maciej Suminski <[email protected]>
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 3
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include "properties_panel.h"
23#include <tool/selection.h>
24#include <eda_base_frame.h>
25#include <eda_item.h>
26#include <import_export.h>
28
29#include <algorithm>
30#include <set>
31
32#include <wx/settings.h>
33#include <wx/stattext.h>
34#include <wx/propgrid/advprops.h>
35
36
37// This is provided by wx >3.3.0
38#if !wxCHECK_VERSION( 3, 3, 0 )
39extern APIIMPORT wxPGGlobalVarsClass* wxPGGlobalVars;
40#endif
41
43 wxPanel( aParent ),
44 m_frame( aFrame ),
45 m_splitter_key_proportion( -1 )
46{
47 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
48
49 // on some platforms wxPGGlobalVars is initialized automatically,
50 // but others need an explicit init
51 if( !wxPGGlobalVars )
52 wxPGInitResourceModule();
53
54 // See https://gitlab.com/kicad/code/kicad/-/issues/12297
55 // and https://github.com/wxWidgets/wxWidgets/issues/11787
56 if( wxPGGlobalVars->m_mapEditorClasses.empty() )
57 {
58 wxPGEditor_TextCtrl = nullptr;
59 wxPGEditor_Choice = nullptr;
60 wxPGEditor_ComboBox = nullptr;
61 wxPGEditor_TextCtrlAndButton = nullptr;
62 wxPGEditor_CheckBox = nullptr;
63 wxPGEditor_ChoiceAndButton = nullptr;
64 wxPGEditor_SpinCtrl = nullptr;
65 wxPGEditor_DatePickerCtrl = nullptr;
66 }
67
68 if( !dynamic_cast<PG_CELL_RENDERER*>( wxPGGlobalVars->m_defaultRenderer ) )
69 {
70 delete wxPGGlobalVars->m_defaultRenderer;
71 wxPGGlobalVars->m_defaultRenderer = new PG_CELL_RENDERER();
72 }
73
74 m_caption = new wxStaticText( this, wxID_ANY, _( "No objects selected" ) );
75 mainSizer->Add( m_caption, 0, wxALL | wxEXPAND, 5 );
76
77 m_grid = new wxPropertyGrid( this, wxID_ANY, wxDefaultPosition, wxSize( 300, 400 ),
78 wxPG_DEFAULT_STYLE );
79 m_grid->SetUnspecifiedValueAppearance( wxPGCell( wxT( "<...>" ) ) );
80 m_grid->SetExtraStyle( wxPG_EX_HELP_AS_TOOLTIPS );
81
82#if wxCHECK_VERSION( 3, 3, 0 )
83 m_grid->SetValidationFailureBehavior( wxPGVFBFlags::MarkCell );
84#else
85 m_grid->SetValidationFailureBehavior( wxPG_VFB_MARK_CELL );
86#endif
87
88 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_RETURN );
89 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_NUMPAD_ENTER );
90 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_DOWN );
91 m_grid->AddActionTrigger( wxPG_ACTION_PREV_PROPERTY, WXK_UP );
92 m_grid->AddActionTrigger( wxPG_ACTION_EDIT, WXK_SPACE );
93 m_grid->DedicateKey( WXK_RETURN );
94 m_grid->DedicateKey( WXK_NUMPAD_ENTER );
95 m_grid->DedicateKey( WXK_DOWN );
96 m_grid->DedicateKey( WXK_UP );
97 mainSizer->Add( m_grid, 1, wxEXPAND, 5 );
98
99 m_grid->SetCellDisabledTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
100
101#ifdef __WXGTK__
102 // Needed for dark mode, on wx 3.0 at least.
103 m_grid->SetCaptionTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_CAPTIONTEXT ) );
104#endif
105
106 SetFont( KIUI::GetDockedPaneFont( this ) );
107
108 SetSizer( mainSizer );
109 Layout();
110
111 m_grid->CenterSplitter();
112
113 Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( PROPERTIES_PANEL::onCharHook ), nullptr, this );
114 Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( PROPERTIES_PANEL::valueChanged ), nullptr, this );
115 Connect( wxEVT_PG_CHANGING, wxPropertyGridEventHandler( PROPERTIES_PANEL::valueChanging ), nullptr, this );
116 Connect( wxEVT_SHOW, wxShowEventHandler( PROPERTIES_PANEL::onShow ), nullptr, this );
117
118 Bind( wxEVT_PG_COL_END_DRAG,
119 [&]( wxPropertyGridEvent& )
120 {
122 static_cast<float>( m_grid->GetSplitterPosition() ) / m_grid->GetSize().x;
123 } );
124
125 Bind( wxEVT_SIZE,
126 [&]( wxSizeEvent& aEvent )
127 {
128 CallAfter( [&]()
129 {
131 } );
132 aEvent.Skip();
133 } );
134}
135
136
138{
139 UpdateData();
140}
141
142
144{
145 auto reset =
146 [&]()
147 {
148 if( m_grid->IsEditorFocused() )
149 m_grid->CommitChangesFromEditor();
150
151 m_grid->Clear();
152 m_displayed.clear();
153 };
154
155 if( aSelection.Empty() )
156 {
157 m_caption->SetLabel( _( "No objects selected" ) );
158 reset();
159 return;
160 }
161
162 // Get all the selected types
163 std::set<TYPE_ID> types;
164
165 for( EDA_ITEM* item : aSelection )
166 types.insert( TYPE_HASH( *item ) );
167
168 wxCHECK( !types.empty(), /* void */ );
169
171 propMgr.SetUnits( m_frame->GetUserUnits() );
173
174 std::set<PROPERTY_BASE*> commonProps;
175 const PROPERTY_LIST& allProperties = propMgr.GetProperties( *types.begin() );
176 copy( allProperties.begin(), allProperties.end(), inserter( commonProps, commonProps.begin() ) );
177
178 PROPERTY_DISPLAY_ORDER displayOrder = propMgr.GetDisplayOrder( *types.begin() );
179
180 std::vector<wxString> groupDisplayOrder = propMgr.GetGroupDisplayOrder( *types.begin() );
181 std::set<wxString> groups( groupDisplayOrder.begin(), groupDisplayOrder.end() );
182
183 std::set<PROPERTY_BASE*> availableProps;
184
185 // Get all possible properties
186 for( const TYPE_ID& type : types )
187 {
188 const PROPERTY_LIST& itemProps = propMgr.GetProperties( type );
189
190 const PROPERTY_DISPLAY_ORDER& itemDisplayOrder = propMgr.GetDisplayOrder( type );
191
192 copy( itemDisplayOrder.begin(), itemDisplayOrder.end(),
193 inserter( displayOrder, displayOrder.begin() ) );
194
195 const std::vector<wxString>& itemGroups = propMgr.GetGroupDisplayOrder( type );
196
197 for( const wxString& group : itemGroups )
198 {
199 if( !groups.count( group ) )
200 {
201 groupDisplayOrder.emplace_back( group );
202 groups.insert( group );
203 }
204 }
205
206 for( auto it = commonProps.begin(); it != commonProps.end(); /* ++it in the loop */ )
207 {
208 if( !binary_search( itemProps.begin(), itemProps.end(), *it ) )
209 it = commonProps.erase( it );
210 else
211 ++it;
212 }
213 }
214
215 EDA_ITEM* firstItem = aSelection.Front();
216 bool isFootprintEditor = m_frame->IsType( FRAME_FOOTPRINT_EDITOR );
217
218 // Find a set of properties that is common to all selected items
219 for( PROPERTY_BASE* property : commonProps )
220 {
221 if( property->IsHiddenFromPropertiesManager() )
222 continue;
223
224 if( isFootprintEditor && property->IsHiddenFromLibraryEditors() )
225 continue;
226
227 if( propMgr.IsAvailableFor( TYPE_HASH( *firstItem ), property, firstItem ) )
228 availableProps.insert( property );
229 }
230
231 bool writeable = true;
232 std::set<PROPERTY_BASE*> existingProps;
233
234 for( wxPropertyGridIterator it = m_grid->GetIterator(); !it.AtEnd(); it.Next() )
235 {
236 wxPGProperty* pgProp = it.GetProperty();
237 PROPERTY_BASE* property = propMgr.GetProperty( TYPE_HASH( *firstItem ), pgProp->GetName() );
238
239 // Switching item types? Property may no longer be valid
240 if( !property )
241 continue;
242
243 wxVariant commonVal;
244
245 extractValueAndWritability( aSelection, property, commonVal, writeable );
246 pgProp->SetValue( commonVal );
247 pgProp->Enable( writeable );
248
249 existingProps.insert( property );
250 }
251
252 if( existingProps == availableProps )
253 return;
254
255 // Some difference exists: start from scratch
256 reset();
257
258 std::map<wxPGProperty*, int> pgPropOrders;
259 std::map<wxString, std::vector<wxPGProperty*>> pgPropGroups;
260
261 for( PROPERTY_BASE* property : availableProps )
262 {
263 wxPGProperty* pgProp = createPGProperty( property );
264 wxVariant commonVal;
265
266 if( !extractValueAndWritability( aSelection, property, commonVal, writeable ) )
267 continue;
268
269 if( pgProp )
270 {
271 pgProp->SetValue( commonVal );
272 pgProp->Enable( writeable );
273 m_displayed.push_back( property );
274
275 wxASSERT( displayOrder.count( property ) );
276 pgPropOrders[pgProp] = displayOrder[property];
277 pgPropGroups[property->Group()].emplace_back( pgProp );
278 }
279 }
280
281 if( aSelection.Size() > 1 )
282 {
283 m_caption->SetLabel( wxString::Format( _( "%d objects selected" ), aSelection.Size() ) );
284 }
285 else
286 {
287 m_caption->SetLabel( aSelection.Front()->GetFriendlyName() );
288 }
289
290 const wxString unspecifiedGroupCaption = _( "Basic Properties" );
291
292 for( const wxString& groupName : groupDisplayOrder )
293 {
294 if( !pgPropGroups.count( groupName ) )
295 continue;
296
297 std::vector<wxPGProperty*>& properties = pgPropGroups[groupName];
298 wxString groupCaption = wxGetTranslation( groupName );
299
300 auto groupItem = new wxPropertyCategory( groupName.IsEmpty() ? unspecifiedGroupCaption
301 : groupCaption );
302
303 m_grid->Append( groupItem );
304
305 std::sort( properties.begin(), properties.end(),
306 [&]( wxPGProperty*& aFirst, wxPGProperty*& aSecond )
307 {
308 return pgPropOrders[aFirst] < pgPropOrders[aSecond];
309 } );
310
311 for( wxPGProperty* property : properties )
312 m_grid->Append( property );
313 }
314
316}
317
318
319bool PROPERTIES_PANEL::getItemValue( EDA_ITEM* aItem, PROPERTY_BASE* aProperty, wxVariant& aValue )
320{
321 const wxAny& any = aItem->Get( aProperty );
322 bool converted = false;
323
324 if( aProperty->HasChoices() )
325 {
326 // handle enums as ints, since there are no default conversion functions for wxAny
327 int tmp;
328 converted = any.GetAs<int>( &tmp );
329
330 if( converted )
331 aValue = wxVariant( tmp );
332 }
333
334 if( !converted ) // all other types
335 converted = any.GetAs( &aValue );
336
337 if( !converted )
338 wxFAIL_MSG( wxS( "Could not convert wxAny to wxVariant" ) );
339
340 return converted;
341}
342
343
345 PROPERTY_BASE* aProperty,
346 wxVariant& aValue, bool& aWritable )
347{
349 propMgr.SetUnits( m_frame->GetUserUnits() );
351
352 bool different = false;
353 wxVariant commonVal;
354
355 aWritable = true;
356
357 for( EDA_ITEM* item : aSelection )
358 {
359 if( !propMgr.IsAvailableFor( TYPE_HASH( *item ), aProperty, item ) )
360 return false;
361
362 // If read-only for any of the selection, read-only for the whole selection.
363 if( !aProperty->Writeable( item ) )
364 aWritable = false;
365
366 wxVariant value;
367
368 if( getItemValue( item, aProperty, value ) )
369 {
370 // Null value indicates different property values between items
371 if( !different && !aValue.IsNull() && value != aValue )
372 {
373 different = true;
374 aValue.MakeNull();
375 }
376 else if( !different )
377 {
378 aValue = value;
379 }
380 }
381 else
382 {
383 // getItemValue returned false -- not available for this item
384 return false;
385 }
386 }
387
388 return true;
389}
390
391
392void PROPERTIES_PANEL::onShow( wxShowEvent& aEvent )
393{
394 if( aEvent.IsShown() )
395 UpdateData();
396}
397
398
399void PROPERTIES_PANEL::onCharHook( wxKeyEvent& aEvent )
400{
401 if( aEvent.GetKeyCode() == WXK_TAB && !aEvent.ShiftDown() )
402 {
403 m_grid->CommitChangesFromEditor();
404 return;
405 }
406
407 if( aEvent.GetKeyCode() == WXK_SPACE )
408 {
409 if( wxPGProperty* prop = m_grid->GetSelectedProperty() )
410 {
411 if( prop->GetValueType() == wxT( "bool" ) )
412 {
413 m_grid->SetPropertyValue( prop, !prop->GetValue().GetBool() );
414 return;
415 }
416 }
417 }
418
419 if( aEvent.GetKeyCode() == WXK_RETURN || aEvent.GetKeyCode() == WXK_NUMPAD_ENTER )
420 {
421 m_grid->CommitChangesFromEditor();
422 /* don't skip this one; if we're not the last property we'll also go to the next row */
423 }
424
425 aEvent.Skip();
426}
427
428
430{
432 m_grid->CenterSplitter();
433 else
434 m_grid->SetSplitterPosition( m_splitter_key_proportion * m_grid->GetSize().x );
435}
436
437
439{
440 m_splitter_key_proportion = aProportion;
442}
The base frame for deriving all KiCad main window classes.
ORIGIN_TRANSFORMS & GetOriginTransforms() override
Return a reference to the default ORIGIN_TRANSFORMS object.
bool IsType(FRAME_T aType) const
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:85
virtual wxString GetFriendlyName() const
Definition: eda_item.cpp:310
wxAny Get(PROPERTY_BASE *aProperty) const
Definition: inspectable.h:84
Enhanced renderer to work around some limitations in wxWidgets 3.0 capabilities.
float m_splitter_key_proportion
Proportion of the grid column splitter that is used for the key column (0.0 - 1.0)
PROPERTIES_PANEL(wxWindow *aParent, EDA_BASE_FRAME *aFrame)
virtual void valueChanging(wxPropertyGridEvent &aEvent)
bool extractValueAndWritability(const SELECTION &aSelection, PROPERTY_BASE *aProperty, wxVariant &aValue, bool &aWritable)
Processes a selection and determines whether the given property should be available or not and what t...
wxPropertyGrid * m_grid
wxStaticText * m_caption
void SetSplitterProportion(float aProportion)
std::vector< PROPERTY_BASE * > m_displayed
virtual void UpdateData()=0
virtual void valueChanged(wxPropertyGridEvent &aEvent)
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...
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 onShow(wxShowEvent &aEvent)
EDA_BASE_FRAME * m_frame
virtual bool HasChoices() const
Return true if this PROPERTY has a limited set of possible values.
Definition: property.h:225
virtual bool Writeable(INSPECTABLE *aObject) const
Definition: property.h:247
Provide class metadata.Helper macro to map type hashes to names.
Definition: property_mgr.h:74
const PROPERTY_LIST & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
const PROPERTY_DISPLAY_ORDER & GetDisplayOrder(TYPE_ID aType) const
static PROPERTY_MANAGER & Instance()
Definition: property_mgr.h:76
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 ...
void SetTransforms(ORIGIN_TRANSFORMS *aTransforms)
Definition: property_mgr.h:225
void SetUnits(EDA_UNITS aUnits)
Definition: property_mgr.h:219
const std::vector< wxString > & GetGroupDisplayOrder(TYPE_ID aType) const
EDA_ITEM * Front() const
Definition: selection.h:208
int Size() const
Returns the number of selected parts.
Definition: selection.h:115
bool Empty() const
Checks if there is anything selected.
Definition: selection.h:109
EDA_UNITS GetUserUnits() const
#define _(s)
Base window classes and related definitions.
@ FRAME_FOOTPRINT_EDITOR
Definition: frame_type.h:41
#define APIIMPORT
Definition: import_export.h:45
wxFont GetDockedPaneFont(wxWindow *aWindow)
Definition: ui_common.cpp:144
APIIMPORT wxPGGlobalVarsClass * wxPGGlobalVars
#define TYPE_HASH(x)
Definition: property.h:63
std::vector< PROPERTY_BASE * > PROPERTY_LIST
Definition: property_mgr.h:48
std::map< PROPERTY_BASE *, int > PROPERTY_DISPLAY_ORDER
Definition: property_mgr.h:57
size_t TYPE_ID
Unique type identifier.
Definition: property_mgr.h:46