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 The 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>
27#include <pgm_base.h>
29
30#include <algorithm>
31#include <set>
32
33#include <wx/settings.h>
34#include <wx/stattext.h>
35#include <wx/propgrid/advprops.h>
36
37
38// This is provided by wx >3.3.0
39#if !wxCHECK_VERSION( 3, 3, 0 )
40extern APIIMPORT wxPGGlobalVarsClass* wxPGGlobalVars;
41#endif
42
44 wxPanel( aParent ),
45 m_frame( aFrame ),
46 m_splitter_key_proportion( -1 )
47{
48 wxBoxSizer* mainSizer = new wxBoxSizer( wxVERTICAL );
49
50 // on some platforms wxPGGlobalVars is initialized automatically,
51 // but others need an explicit init
52 if( !wxPGGlobalVars )
53 wxPGInitResourceModule();
54
55 // See https://gitlab.com/kicad/code/kicad/-/issues/12297
56 // and https://github.com/wxWidgets/wxWidgets/issues/11787
57 if( wxPGGlobalVars->m_mapEditorClasses.empty() )
58 {
59 wxPGEditor_TextCtrl = nullptr;
60 wxPGEditor_Choice = nullptr;
61 wxPGEditor_ComboBox = nullptr;
62 wxPGEditor_TextCtrlAndButton = nullptr;
63 wxPGEditor_CheckBox = nullptr;
64 wxPGEditor_ChoiceAndButton = nullptr;
65 wxPGEditor_SpinCtrl = nullptr;
66 wxPGEditor_DatePickerCtrl = nullptr;
67 }
68
69 if( !Pgm().m_PropertyGridInitialized )
70 {
71 delete wxPGGlobalVars->m_defaultRenderer;
72 wxPGGlobalVars->m_defaultRenderer = new PG_CELL_RENDERER();
74 }
75
76 m_caption = new wxStaticText( this, wxID_ANY, _( "No objects selected" ) );
77 mainSizer->Add( m_caption, 0, wxALL | wxEXPAND, 5 );
78
79 m_grid = new wxPropertyGrid( this );
80 m_grid->SetUnspecifiedValueAppearance( wxPGCell( wxT( "<...>" ) ) );
81 m_grid->SetExtraStyle( wxPG_EX_HELP_AS_TOOLTIPS );
82
83#if wxCHECK_VERSION( 3, 3, 0 )
84 m_grid->SetValidationFailureBehavior( wxPGVFBFlags::MarkCell );
85#else
86 m_grid->SetValidationFailureBehavior( wxPG_VFB_MARK_CELL );
87#endif
88
89#if wxCHECK_VERSION( 3, 3, 0 )
90 m_grid->AddActionTrigger( wxPGKeyboardAction::NextProperty, WXK_RETURN );
91 m_grid->AddActionTrigger( wxPGKeyboardAction::NextProperty, WXK_NUMPAD_ENTER );
92 m_grid->AddActionTrigger( wxPGKeyboardAction::NextProperty, WXK_DOWN );
93 m_grid->AddActionTrigger( wxPGKeyboardAction::PrevProperty, WXK_UP );
94 m_grid->AddActionTrigger( wxPGKeyboardAction::Edit, WXK_SPACE );
95#else
96 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_RETURN );
97 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_NUMPAD_ENTER );
98 m_grid->AddActionTrigger( wxPG_ACTION_NEXT_PROPERTY, WXK_DOWN );
99 m_grid->AddActionTrigger( wxPG_ACTION_PREV_PROPERTY, WXK_UP );
100 m_grid->AddActionTrigger( wxPG_ACTION_EDIT, WXK_SPACE );
101#endif
102
103 m_grid->DedicateKey( WXK_RETURN );
104 m_grid->DedicateKey( WXK_NUMPAD_ENTER );
105 m_grid->DedicateKey( WXK_DOWN );
106 m_grid->DedicateKey( WXK_UP );
107 mainSizer->Add( m_grid, 1, wxEXPAND, 5 );
108
109 m_grid->SetCellDisabledTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
110
111#ifdef __WXGTK__
112 // Needed for dark mode, on wx 3.0 at least.
113 m_grid->SetCaptionTextColour( wxSystemSettings::GetColour( wxSYS_COLOUR_CAPTIONTEXT ) );
114#endif
115
116 SetFont( KIUI::GetDockedPaneFont( this ) );
117
118 SetSizer( mainSizer );
119 Layout();
120
121 m_grid->CenterSplitter();
122
123 Connect( wxEVT_CHAR_HOOK, wxKeyEventHandler( PROPERTIES_PANEL::onCharHook ), nullptr, this );
124 Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( PROPERTIES_PANEL::valueChanged ),
125 nullptr, this );
126 Connect( wxEVT_PG_CHANGING, wxPropertyGridEventHandler( PROPERTIES_PANEL::valueChanging ),
127 nullptr, this );
128 Connect( wxEVT_SHOW, wxShowEventHandler( PROPERTIES_PANEL::onShow ), nullptr, this );
129
130 Bind( wxEVT_PG_COL_END_DRAG,
131 [&]( wxPropertyGridEvent& )
132 {
134 static_cast<float>( m_grid->GetSplitterPosition() ) / m_grid->GetSize().x;
135 } );
136
137 Bind( wxEVT_SIZE,
138 [&]( wxSizeEvent& aEvent )
139 {
140 CallAfter( [this]()
141 {
143 } );
144 aEvent.Skip();
145 } );
146
147 m_frame->Bind( EDA_LANG_CHANGED, &PROPERTIES_PANEL::OnLanguageChanged, this );
148}
149
150
152{
153 m_frame->Unbind( EDA_LANG_CHANGED, &PROPERTIES_PANEL::OnLanguageChanged, this );
154}
155
156
157void PROPERTIES_PANEL::OnLanguageChanged( wxCommandEvent& aEvent )
158{
159 if( m_grid->IsEditorFocused() )
160 m_grid->CommitChangesFromEditor();
161
162 m_grid->Clear();
163 m_displayed.clear();
164
165 UpdateData();
166
167 aEvent.Skip();
168}
169
170
172{
173 auto reset =
174 [&]()
175 {
176 if( m_grid->IsEditorFocused() )
177 m_grid->CommitChangesFromEditor();
178
179 m_grid->Clear();
180 m_displayed.clear();
181 };
182
183 if( aSelection.Empty() )
184 {
185 m_caption->SetLabel( _( "No objects selected" ) );
186 reset();
187 return;
188 }
189 else if( aSelection.Size() == 1 )
190 {
191 m_caption->SetLabel( aSelection.Front()->GetFriendlyName() );
192 }
193 else
194 {
195 m_caption->SetLabel( wxString::Format( _( "%d objects selected" ), aSelection.Size() ) );
196 }
197
198 // Get all the selected types
199 std::set<TYPE_ID> types;
200
201 for( EDA_ITEM* item : aSelection )
202 types.insert( TYPE_HASH( *item ) );
203
204 wxCHECK( !types.empty(), /* void */ ); // already guarded above, but Coverity doesn't know that
205
207 std::set<PROPERTY_BASE*> commonProps;
208 const PROPERTY_LIST& allProperties = propMgr.GetProperties( *types.begin() );
209
210 copy( allProperties.begin(), allProperties.end(),
211 inserter( commonProps, commonProps.begin() ) );
212
213 PROPERTY_DISPLAY_ORDER displayOrder = propMgr.GetDisplayOrder( *types.begin() );
214
215 std::vector<wxString> groupDisplayOrder = propMgr.GetGroupDisplayOrder( *types.begin() );
216 std::set<wxString> groups( groupDisplayOrder.begin(), groupDisplayOrder.end() );
217
218 std::set<PROPERTY_BASE*> availableProps;
219
220 // Get all possible properties
221 for( const TYPE_ID& type : types )
222 {
223 const PROPERTY_LIST& itemProps = propMgr.GetProperties( type );
224
225 const PROPERTY_DISPLAY_ORDER& itemDisplayOrder = propMgr.GetDisplayOrder( type );
226
227 copy( itemDisplayOrder.begin(), itemDisplayOrder.end(),
228 inserter( displayOrder, displayOrder.begin() ) );
229
230 const std::vector<wxString>& itemGroups = propMgr.GetGroupDisplayOrder( type );
231
232 for( const wxString& group : itemGroups )
233 {
234 if( !groups.count( group ) )
235 {
236 groupDisplayOrder.emplace_back( group );
237 groups.insert( group );
238 }
239 }
240
241 for( auto it = commonProps.begin(); it != commonProps.end(); /* ++it in the loop */ )
242 {
243 if( !binary_search( itemProps.begin(), itemProps.end(), *it ) )
244 it = commonProps.erase( it );
245 else
246 ++it;
247 }
248 }
249
250 EDA_ITEM* firstItem = aSelection.Front();
251
252 bool isLibraryEditor = m_frame->IsType( FRAME_FOOTPRINT_EDITOR )
254
255 bool isDesignEditor = m_frame->IsType( FRAME_PCB_EDITOR )
256 || m_frame->IsType( FRAME_SCH );
257
258 // Find a set of properties that is common to all selected items
259 for( PROPERTY_BASE* property : commonProps )
260 {
261 if( property->IsHiddenFromPropertiesManager() )
262 continue;
263
264 if( isLibraryEditor && property->IsHiddenFromLibraryEditors() )
265 continue;
266
267 if( isDesignEditor && property->IsHiddenFromDesignEditors() )
268 continue;
269
270 if( propMgr.IsAvailableFor( TYPE_HASH( *firstItem ), property, firstItem ) )
271 availableProps.insert( property );
272 }
273
274 bool writeable = true;
275 std::set<PROPERTY_BASE*> existingProps;
276
277 for( wxPropertyGridIterator it = m_grid->GetIterator(); !it.AtEnd(); it.Next() )
278 {
279 wxPGProperty* pgProp = it.GetProperty();
280 PROPERTY_BASE* property = propMgr.GetProperty( TYPE_HASH( *firstItem ), pgProp->GetName() );
281
282 // Switching item types? Property may no longer be valid
283 if( !property )
284 continue;
285
286 wxVariant commonVal;
287
288 extractValueAndWritability( aSelection, property, commonVal, writeable );
289 pgProp->SetValue( commonVal );
290 pgProp->Enable( writeable );
291
292 existingProps.insert( property );
293 }
294
295 if( !existingProps.empty() && existingProps == availableProps )
296 return;
297
298 // Some difference exists: start from scratch
299 reset();
300
301 std::map<wxPGProperty*, int> pgPropOrders;
302 std::map<wxString, std::vector<wxPGProperty*>> pgPropGroups;
303
304 for( PROPERTY_BASE* property : availableProps )
305 {
306 wxPGProperty* pgProp = createPGProperty( property );
307 wxVariant commonVal;
308
309 if( !extractValueAndWritability( aSelection, property, commonVal, writeable ) )
310 continue;
311
312 if( pgProp )
313 {
314 pgProp->SetValue( commonVal );
315 pgProp->Enable( writeable );
316 m_displayed.push_back( property );
317
318 wxASSERT( displayOrder.count( property ) );
319 pgPropOrders[pgProp] = displayOrder[property];
320 pgPropGroups[property->Group()].emplace_back( pgProp );
321 }
322 }
323
324 const wxString unspecifiedGroupCaption = _( "Basic Properties" );
325
326 for( const wxString& groupName : groupDisplayOrder )
327 {
328 if( !pgPropGroups.count( groupName ) )
329 continue;
330
331 std::vector<wxPGProperty*>& properties = pgPropGroups[groupName];
332 wxString groupCaption = wxGetTranslation( groupName );
333
334 auto groupItem = new wxPropertyCategory( groupName.IsEmpty() ? unspecifiedGroupCaption
335 : groupCaption );
336
337 m_grid->Append( groupItem );
338
339 std::sort( properties.begin(), properties.end(),
340 [&]( wxPGProperty*& aFirst, wxPGProperty*& aSecond )
341 {
342 return pgPropOrders[aFirst] < pgPropOrders[aSecond];
343 } );
344
345 for( wxPGProperty* property : properties )
346 m_grid->Append( property );
347 }
348
350}
351
352
353bool PROPERTIES_PANEL::getItemValue( EDA_ITEM* aItem, PROPERTY_BASE* aProperty, wxVariant& aValue )
354{
355 const wxAny& any = aItem->Get( aProperty );
356 bool converted = false;
357
358 if( aProperty->HasChoices() )
359 {
360 // handle enums as ints, since there are no default conversion functions for wxAny
361 int tmp;
362 converted = any.GetAs<int>( &tmp );
363
364 if( converted )
365 aValue = wxVariant( tmp );
366 }
367
368 if( !converted ) // all other types
369 converted = any.GetAs( &aValue );
370
371 if( !converted )
372 wxFAIL_MSG( wxS( "Could not convert wxAny to wxVariant" ) );
373
374 return converted;
375}
376
377
379 PROPERTY_BASE* aProperty,
380 wxVariant& aValue, bool& aWritable )
381{
383 bool different = false;
384 wxVariant commonVal;
385
386 aWritable = true;
387
388 for( EDA_ITEM* item : aSelection )
389 {
390 if( !propMgr.IsAvailableFor( TYPE_HASH( *item ), aProperty, item ) )
391 return false;
392
393 if( aProperty->IsHiddenFromPropertiesManager() )
394 return false;
395
396 // If read-only for any of the selection, read-only for the whole selection.
397 if( !propMgr.IsWriteableFor( TYPE_HASH( *item ), aProperty, item ) )
398 aWritable = false;
399
400 wxVariant value;
401
402 if( getItemValue( item, aProperty, value ) )
403 {
404 // Null value indicates different property values between items
405 if( !different && !aValue.IsNull() && value != aValue )
406 {
407 different = true;
408 aValue.MakeNull();
409 }
410 else if( !different )
411 {
412 aValue = value;
413 }
414 }
415 else
416 {
417 // getItemValue returned false -- not available for this item
418 return false;
419 }
420 }
421
422 return true;
423}
424
425
426void PROPERTIES_PANEL::onShow( wxShowEvent& aEvent )
427{
428 if( aEvent.IsShown() )
429 UpdateData();
430
431 aEvent.Skip();
432}
433
434
435void PROPERTIES_PANEL::onCharHook( wxKeyEvent& aEvent )
436{
437 if( aEvent.GetKeyCode() == WXK_TAB && !aEvent.ShiftDown() && m_grid->IsAnyModified() )
438 {
439 m_grid->CommitChangesFromEditor();
440
441 // Pass the tab key on so the default property grid tab behavior is honored.
442 aEvent.Skip();
443 return;
444 }
445
446 if( aEvent.GetKeyCode() == WXK_SPACE )
447 {
448 if( wxPGProperty* prop = m_grid->GetSelectedProperty() )
449 {
450 if( prop->GetValueType() == wxT( "bool" ) )
451 {
452 m_grid->SetPropertyValue( prop, !prop->GetValue().GetBool() );
453 return;
454 }
455 }
456 }
457
458 if( aEvent.GetKeyCode() == WXK_RETURN || aEvent.GetKeyCode() == WXK_NUMPAD_ENTER )
459 {
460 m_grid->CommitChangesFromEditor();
461 /* don't skip this one; if we're not the last property we'll also go to the next row */
462 }
463
464 aEvent.Skip();
465}
466
467
469{
471 m_grid->CenterSplitter();
472 else
473 m_grid->SetSplitterPosition( m_splitter_key_proportion * m_grid->GetSize().x );
474}
475
476
478{
479 m_splitter_key_proportion = aProportion;
481}
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:89
virtual wxString GetFriendlyName() const
Definition: eda_item.cpp:332
wxAny Get(PROPERTY_BASE *aProperty) const
Definition: inspectable.h:100
bool m_PropertyGridInitialized
Definition: pgm_base.h:384
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 OnLanguageChanged(wxCommandEvent &aEvent)
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
bool IsHiddenFromPropertiesManager() const
Definition: property.h:299
virtual bool HasChoices() const
Return true if this PROPERTY has a limited set of possible values.
Definition: property.h:241
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:172
int Size() const
Returns the number of selected parts.
Definition: selection.h:116
bool Empty() const
Checks if there is anything selected.
Definition: selection.h:110
#define _(s)
Base window classes and related definitions.
@ FRAME_PCB_EDITOR
Definition: frame_type.h:42
@ FRAME_SCH_SYMBOL_EDITOR
Definition: frame_type.h:35
@ FRAME_SCH
Definition: frame_type.h:34
@ FRAME_FOOTPRINT_EDITOR
Definition: frame_type.h:43
#define APIIMPORT
Definition: import_export.h:43
KICOMMON_API wxFont GetDockedPaneFont(wxWindow *aWindow)
Definition: ui_common.cpp:143
PGM_BASE & Pgm()
The global program "get" accessor.
Definition: pgm_base.cpp:1073
see class PGM_BASE
APIIMPORT wxPGGlobalVarsClass * wxPGGlobalVars
#define TYPE_HASH(x)
Definition: property.h:71
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