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-2023 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 );
78 m_grid->SetUnspecifiedValueAppearance( wxPGCell( wxT( "<...>" ) ) );
79 m_grid->SetExtraStyle( wxPG_EX_HELP_AS_TOOLTIPS );
80
81#if wxCHECK_VERSION( 3, 3, 0 )
82 m_grid->SetValidationFailureBehavior( wxPGVFBFlags::MarkCell );
83#else
84 m_grid->SetValidationFailureBehavior( wxPG_VFB_MARK_CELL );
85#endif
86
87#if wxCHECK_VERSION( 3, 3, 0 )
88 m_grid->AddActionTrigger( wxPGKeyboardActions::NextProperty, WXK_RETURN );
89 m_grid->AddActionTrigger( wxPGKeyboardActions::NextProperty, WXK_NUMPAD_ENTER );
90 m_grid->AddActionTrigger( wxPGKeyboardActions::NextProperty, WXK_DOWN );
91 m_grid->AddActionTrigger( wxPGKeyboardActions::PrevProperty, WXK_UP );
92 m_grid->AddActionTrigger( wxPGKeyboardActions::Edit, WXK_SPACE );
93#else
94 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_RETURN );
95 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_NUMPAD_ENTER );
96 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_DOWN );
97 m_grid->AddActionTrigger( wxPG_ACTION_PREV_PROPERTY, WXK_UP );
98 m_grid->AddActionTrigger( wxPG_ACTION_EDIT, WXK_SPACE );
99#endif
100
101 m_grid->DedicateKey( WXK_RETURN );
102 m_grid->DedicateKey( WXK_NUMPAD_ENTER );
103 m_grid->DedicateKey( WXK_DOWN );
104 m_grid->DedicateKey( WXK_UP );
105 mainSizer->Add( m_grid, 1, wxEXPAND, 5 );
106
107 m_grid->SetCellDisabledTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
108
109#ifdef __WXGTK__
110 // Needed for dark mode, on wx 3.0 at least.
111 m_grid->SetCaptionTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_CAPTIONTEXT ) );
112#endif
113
114 SetFont( KIUI::GetDockedPaneFont( this ) );
115
116 SetSizer( mainSizer );
117 Layout();
118
119 m_grid->CenterSplitter();
120
121 Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( PROPERTIES_PANEL::onCharHook ), nullptr, this );
122 Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( PROPERTIES_PANEL::valueChanged ), nullptr, this );
123 Connect( wxEVT_PG_CHANGING, wxPropertyGridEventHandler( PROPERTIES_PANEL::valueChanging ), nullptr, this );
124 Connect( wxEVT_SHOW, wxShowEventHandler( PROPERTIES_PANEL::onShow ), nullptr, this );
125
126 Bind( wxEVT_PG_COL_END_DRAG,
127 [&]( wxPropertyGridEvent& )
128 {
130 static_cast<float>( m_grid->GetSplitterPosition() ) / m_grid->GetSize().x;
131 } );
132
133 Bind( wxEVT_SIZE,
134 [&]( wxSizeEvent& aEvent )
135 {
136 CallAfter( [&]()
137 {
139 } );
140 aEvent.Skip();
141 } );
142}
143
144
146{
147 UpdateData();
148}
149
150
152{
153 auto reset =
154 [&]()
155 {
156 if( m_grid->IsEditorFocused() )
157 m_grid->CommitChangesFromEditor();
158
159 m_grid->Clear();
160 m_displayed.clear();
161 };
162
163 if( aSelection.Empty() )
164 {
165 m_caption->SetLabel( _( "No objects selected" ) );
166 reset();
167 return;
168 }
169
170 // Get all the selected types
171 std::set<TYPE_ID> types;
172
173 for( EDA_ITEM* item : aSelection )
174 types.insert( TYPE_HASH( *item ) );
175
176 wxCHECK( !types.empty(), /* void */ );
177
179 std::set<PROPERTY_BASE*> commonProps;
180 const PROPERTY_LIST& allProperties = propMgr.GetProperties( *types.begin() );
181
182 copy( allProperties.begin(), allProperties.end(), inserter( commonProps, commonProps.begin() ) );
183
184 PROPERTY_DISPLAY_ORDER displayOrder = propMgr.GetDisplayOrder( *types.begin() );
185
186 std::vector<wxString> groupDisplayOrder = propMgr.GetGroupDisplayOrder( *types.begin() );
187 std::set<wxString> groups( groupDisplayOrder.begin(), groupDisplayOrder.end() );
188
189 std::set<PROPERTY_BASE*> availableProps;
190
191 // Get all possible properties
192 for( const TYPE_ID& type : types )
193 {
194 const PROPERTY_LIST& itemProps = propMgr.GetProperties( type );
195
196 const PROPERTY_DISPLAY_ORDER& itemDisplayOrder = propMgr.GetDisplayOrder( type );
197
198 copy( itemDisplayOrder.begin(), itemDisplayOrder.end(),
199 inserter( displayOrder, displayOrder.begin() ) );
200
201 const std::vector<wxString>& itemGroups = propMgr.GetGroupDisplayOrder( type );
202
203 for( const wxString& group : itemGroups )
204 {
205 if( !groups.count( group ) )
206 {
207 groupDisplayOrder.emplace_back( group );
208 groups.insert( group );
209 }
210 }
211
212 for( auto it = commonProps.begin(); it != commonProps.end(); /* ++it in the loop */ )
213 {
214 if( !binary_search( itemProps.begin(), itemProps.end(), *it ) )
215 it = commonProps.erase( it );
216 else
217 ++it;
218 }
219 }
220
221 EDA_ITEM* firstItem = aSelection.Front();
222 bool isFootprintEditor = m_frame->IsType( FRAME_FOOTPRINT_EDITOR );
223
224 // Find a set of properties that is common to all selected items
225 for( PROPERTY_BASE* property : commonProps )
226 {
227 if( property->IsHiddenFromPropertiesManager() )
228 continue;
229
230 if( isFootprintEditor && property->IsHiddenFromLibraryEditors() )
231 continue;
232
233 if( propMgr.IsAvailableFor( TYPE_HASH( *firstItem ), property, firstItem ) )
234 availableProps.insert( property );
235 }
236
237 bool writeable = true;
238 std::set<PROPERTY_BASE*> existingProps;
239
240 for( wxPropertyGridIterator it = m_grid->GetIterator(); !it.AtEnd(); it.Next() )
241 {
242 wxPGProperty* pgProp = it.GetProperty();
243 PROPERTY_BASE* property = propMgr.GetProperty( TYPE_HASH( *firstItem ), pgProp->GetName() );
244
245 // Switching item types? Property may no longer be valid
246 if( !property )
247 continue;
248
249 wxVariant commonVal;
250
251 extractValueAndWritability( aSelection, property, commonVal, writeable );
252 pgProp->SetValue( commonVal );
253 pgProp->Enable( writeable );
254
255 existingProps.insert( property );
256 }
257
258 if( !existingProps.empty() && existingProps == availableProps )
259 return;
260
261 // Some difference exists: start from scratch
262 reset();
263
264 std::map<wxPGProperty*, int> pgPropOrders;
265 std::map<wxString, std::vector<wxPGProperty*>> pgPropGroups;
266
267 for( PROPERTY_BASE* property : availableProps )
268 {
269 wxPGProperty* pgProp = createPGProperty( property );
270 wxVariant commonVal;
271
272 if( !extractValueAndWritability( aSelection, property, commonVal, writeable ) )
273 continue;
274
275 if( pgProp )
276 {
277 pgProp->SetValue( commonVal );
278 pgProp->Enable( writeable );
279 m_displayed.push_back( property );
280
281 wxASSERT( displayOrder.count( property ) );
282 pgPropOrders[pgProp] = displayOrder[property];
283 pgPropGroups[property->Group()].emplace_back( pgProp );
284 }
285 }
286
287 if( aSelection.Size() > 1 )
288 {
289 m_caption->SetLabel( wxString::Format( _( "%d objects selected" ), aSelection.Size() ) );
290 }
291 else
292 {
293 m_caption->SetLabel( aSelection.Front()->GetFriendlyName() );
294 }
295
296 const wxString unspecifiedGroupCaption = _( "Basic Properties" );
297
298 for( const wxString& groupName : groupDisplayOrder )
299 {
300 if( !pgPropGroups.count( groupName ) )
301 continue;
302
303 std::vector<wxPGProperty*>& properties = pgPropGroups[groupName];
304 wxString groupCaption = wxGetTranslation( groupName );
305
306 auto groupItem = new wxPropertyCategory( groupName.IsEmpty() ? unspecifiedGroupCaption
307 : groupCaption );
308
309 m_grid->Append( groupItem );
310
311 std::sort( properties.begin(), properties.end(),
312 [&]( wxPGProperty*& aFirst, wxPGProperty*& aSecond )
313 {
314 return pgPropOrders[aFirst] < pgPropOrders[aSecond];
315 } );
316
317 for( wxPGProperty* property : properties )
318 m_grid->Append( property );
319 }
320
322}
323
324
325bool PROPERTIES_PANEL::getItemValue( EDA_ITEM* aItem, PROPERTY_BASE* aProperty, wxVariant& aValue )
326{
327 const wxAny& any = aItem->Get( aProperty );
328 bool converted = false;
329
330 if( aProperty->HasChoices() )
331 {
332 // handle enums as ints, since there are no default conversion functions for wxAny
333 int tmp;
334 converted = any.GetAs<int>( &tmp );
335
336 if( converted )
337 aValue = wxVariant( tmp );
338 }
339
340 if( !converted ) // all other types
341 converted = any.GetAs( &aValue );
342
343 if( !converted )
344 wxFAIL_MSG( wxS( "Could not convert wxAny to wxVariant" ) );
345
346 return converted;
347}
348
349
351 PROPERTY_BASE* aProperty,
352 wxVariant& aValue, bool& aWritable )
353{
355 bool different = false;
356 wxVariant commonVal;
357
358 aWritable = true;
359
360 for( EDA_ITEM* item : aSelection )
361 {
362 if( !propMgr.IsAvailableFor( TYPE_HASH( *item ), aProperty, item ) )
363 return false;
364
365 // If read-only for any of the selection, read-only for the whole selection.
366 if( !propMgr.IsWriteableFor( TYPE_HASH( *item ), aProperty, item ) )
367 aWritable = false;
368
369 wxVariant value;
370
371 if( getItemValue( item, aProperty, value ) )
372 {
373 // Null value indicates different property values between items
374 if( !different && !aValue.IsNull() && value != aValue )
375 {
376 different = true;
377 aValue.MakeNull();
378 }
379 else if( !different )
380 {
381 aValue = value;
382 }
383 }
384 else
385 {
386 // getItemValue returned false -- not available for this item
387 return false;
388 }
389 }
390
391 return true;
392}
393
394
395void PROPERTIES_PANEL::onShow( wxShowEvent& aEvent )
396{
397 if( aEvent.IsShown() )
398 UpdateData();
399}
400
401
402void PROPERTIES_PANEL::onCharHook( wxKeyEvent& aEvent )
403{
404 if( aEvent.GetKeyCode() == WXK_TAB && !aEvent.ShiftDown() )
405 {
406 m_grid->CommitChangesFromEditor();
407 return;
408 }
409
410 if( aEvent.GetKeyCode() == WXK_SPACE )
411 {
412 if( wxPGProperty* prop = m_grid->GetSelectedProperty() )
413 {
414 if( prop->GetValueType() == wxT( "bool" ) )
415 {
416 m_grid->SetPropertyValue( prop, !prop->GetValue().GetBool() );
417 return;
418 }
419 }
420 }
421
422 if( aEvent.GetKeyCode() == WXK_RETURN || aEvent.GetKeyCode() == WXK_NUMPAD_ENTER )
423 {
424 m_grid->CommitChangesFromEditor();
425 /* don't skip this one; if we're not the last property we'll also go to the next row */
426 }
427
428 aEvent.Skip();
429}
430
431
433{
435 m_grid->CenterSplitter();
436 else
437 m_grid->SetSplitterPosition( m_splitter_key_proportion * m_grid->GetSize().x );
438}
439
440
442{
443 m_splitter_key_proportion = aProportion;
445}
The base frame for deriving all KiCad main window classes.
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:329
wxAny Get(PROPERTY_BASE *aProperty) const
Definition: inspectable.h:99
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:233
Provide class metadata.Helper macro to map type hashes to names.
Definition: property_mgr.h:85
const PROPERTY_LIST & GetProperties(TYPE_ID aType) const
Return all properties for a specific type.
const PROPERTY_DISPLAY_ORDER & GetDisplayOrder(TYPE_ID aType) const
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()
Definition: property_mgr.h:87
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::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
#define _(s)
Base window classes and related definitions.
@ FRAME_FOOTPRINT_EDITOR
Definition: frame_type.h:43
#define APIIMPORT
Definition: import_export.h:43
wxFont GetDockedPaneFont(wxWindow *aWindow)
Definition: ui_common.cpp:139
APIIMPORT wxPGGlobalVarsClass * wxPGGlobalVars
#define TYPE_HASH(x)
Definition: property.h:64
std::vector< PROPERTY_BASE * > PROPERTY_LIST
Definition: property_mgr.h:49
std::map< PROPERTY_BASE *, int > PROPERTY_DISPLAY_ORDER
Definition: property_mgr.h:58
size_t TYPE_ID
Unique type identifier.
Definition: property_mgr.h:47