KiCad PCB EDA Suite
Loading...
Searching...
No Matches
file_checklist_ctrl.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <memory>
21
22#include <wx/dataview.h>
23#include <wx/filename.h>
24
27
28
29void FILE_CHECKLIST_DATA::SetEntries( const std::vector<FILE_CHECKLIST_ENTRY>& aEntries )
30{
31 m_sections.clear();
32 m_rowsBySection.clear();
33
34 for( const FILE_CHECKLIST_ENTRY& entry : aEntries )
35 {
36 if( m_rowsBySection.find( entry.section ) == m_rowsBySection.end() )
37 m_sections.push_back( entry.section );
38
39 m_rowsBySection[entry.section].push_back( entry );
40 }
41}
42
43
44const std::vector<FILE_CHECKLIST_ENTRY>& FILE_CHECKLIST_DATA::Rows( const wxString& aSection ) const
45{
46 static const std::vector<FILE_CHECKLIST_ENTRY> empty;
47
48 auto it = m_rowsBySection.find( aSection );
49 return it == m_rowsBySection.end() ? empty : it->second;
50}
51
52
53void FILE_CHECKLIST_DATA::SetChecked( const wxString& aPath, bool aChecked )
54{
55 for( auto& [section, rows] : m_rowsBySection )
56 {
57 for( FILE_CHECKLIST_ENTRY& row : rows )
58 {
59 if( row.path == aPath )
60 {
61 row.checked = aChecked;
62 return;
63 }
64 }
65 }
66}
67
68
69void FILE_CHECKLIST_DATA::SetSectionChecked( const wxString& aSection, bool aChecked )
70{
71 auto it = m_rowsBySection.find( aSection );
72
73 if( it == m_rowsBySection.end() )
74 return;
75
76 for( FILE_CHECKLIST_ENTRY& row : it->second )
77 row.checked = aChecked;
78}
79
80
81bool FILE_CHECKLIST_DATA::IsSectionChecked( const wxString& aSection ) const
82{
83 auto it = m_rowsBySection.find( aSection );
84
85 if( it == m_rowsBySection.end() || it->second.empty() )
86 return false;
87
88 for( const FILE_CHECKLIST_ENTRY& row : it->second )
89 {
90 if( !row.checked )
91 return false;
92 }
93
94 return true;
95}
96
97
98std::vector<wxString> FILE_CHECKLIST_DATA::CheckedPaths() const
99{
100 std::vector<wxString> paths;
101
102 for( const wxString& section : m_sections )
103 {
104 for( const FILE_CHECKLIST_ENTRY& row : m_rowsBySection.at( section ) )
105 {
106 if( row.checked )
107 paths.push_back( row.path );
108 }
109 }
110
111 return paths;
112}
113
114
115// Addressing node for the data view. The row itself lives in FILE_CHECKLIST_DATA, so a
116// node only says which section it belongs to and, for a file row, its index within that
117// section. Indices stay valid because the nodes are rebuilt whenever the rows are.
119{
120 bool isSection = false;
121 wxString section;
122 size_t index = 0;
124 std::vector<std::unique_ptr<CHECKLIST_NODE>> children;
125};
126
127
128class FILE_CHECKLIST_MODEL : public wxDataViewModel
129{
130public:
138
139 void SetEntries( const std::vector<FILE_CHECKLIST_ENTRY>& aEntries )
140 {
141 m_data.SetEntries( aEntries );
142 rebuildNodes();
143 Cleared();
144 }
145
146 void SetAllChecked( bool aChecked )
147 {
148 // Update every row before notifying listeners.
149 for( const std::unique_ptr<CHECKLIST_NODE>& section : m_sectionNodes )
150 m_data.SetSectionChecked( section->section, aChecked );
151
152 for( const std::unique_ptr<CHECKLIST_NODE>& section : m_sectionNodes )
153 {
154 for( const std::unique_ptr<CHECKLIST_NODE>& child : section->children )
155 ValueChanged( wxDataViewItem( child.get() ), COL_CHECK );
156
157 ValueChanged( wxDataViewItem( section.get() ), COL_CHECK );
158 }
159 }
160
161 std::vector<wxString> CheckedPaths() const { return m_data.CheckedPaths(); }
162
163 wxString PathFor( const wxDataViewItem& aItem ) const
164 {
165 CHECKLIST_NODE* node = toNode( aItem );
166
167 if( !node || node->isSection )
168 return wxEmptyString;
169
170 return m_data.Rows( node->section )[node->index].path;
171 }
172
173 unsigned int GetColumnCount() const override { return COL_COUNT; }
174
175 wxString GetColumnType( unsigned int aCol ) const override
176 {
177 switch( aCol )
178 {
179 case COL_CHECK: return wxT( "bool" );
180 case COL_NAME: return wxT( "wxDataViewIconText" );
181 default: return wxT( "string" );
182 }
183 }
184
185 void GetValue( wxVariant& aVariant, const wxDataViewItem& aItem, unsigned int aCol ) const override
186 {
187 CHECKLIST_NODE* node = toNode( aItem );
188
189 if( !node )
190 return;
191
192 if( node->isSection )
193 {
194 switch( aCol )
195 {
196 case COL_CHECK: aVariant = m_data.IsSectionChecked( node->section ); break;
197 case COL_NAME:
198 aVariant << wxDataViewIconText(
199 wxString::Format( wxT( "%s (%zu)" ), node->section, m_data.Rows( node->section ).size() ) );
200 break;
201 default: aVariant = wxString(); break;
202 }
203 }
204 else
205 {
206 const FILE_CHECKLIST_ENTRY& row = m_data.Rows( node->section )[node->index];
207
208 switch( aCol )
209 {
210 case COL_CHECK: aVariant = row.checked; break;
211 case COL_NAME: aVariant << wxDataViewIconText( row.label, KiBitmapBundle( row.icon ) ); break;
212 default: aVariant = row.status; break;
213 }
214 }
215 }
216
217 bool GetAttr( const wxDataViewItem& aItem, unsigned int aCol, wxDataViewItemAttr& aAttr ) const override
218 {
219 CHECKLIST_NODE* node = toNode( aItem );
220
221 if( !node || node->isSection || aCol != COL_STATUS )
222 return false;
223
224 const FILE_CHECKLIST_ENTRY& row = m_data.Rows( node->section )[node->index];
225
226 if( !row.statusColour.IsOk() )
227 return false;
228
229 aAttr.SetColour( row.statusColour );
230 return true;
231 }
232
233 bool SetValue( const wxVariant& aVariant, const wxDataViewItem& aItem, unsigned int aCol ) override
234 {
235 CHECKLIST_NODE* node = toNode( aItem );
236
237 if( !node || aCol != COL_CHECK )
238 return false;
239
240 bool checked = aVariant.GetBool();
241
242 if( node->isSection )
243 {
244 m_data.SetSectionChecked( node->section, checked );
245
246 for( const std::unique_ptr<CHECKLIST_NODE>& child : node->children )
247 ValueChanged( wxDataViewItem( child.get() ), COL_CHECK );
248 }
249 else
250 {
251 m_data.SetChecked( m_data.Rows( node->section )[node->index].path, checked );
252
253 if( node->parent )
254 ValueChanged( wxDataViewItem( node->parent ), COL_CHECK );
255 }
256
257 return true;
258 }
259
260 wxDataViewItem GetParent( const wxDataViewItem& aItem ) const override
261 {
262 CHECKLIST_NODE* node = toNode( aItem );
263 return ( node && node->parent ) ? wxDataViewItem( node->parent ) : wxDataViewItem();
264 }
265
266 bool IsContainer( const wxDataViewItem& aItem ) const override
267 {
268 CHECKLIST_NODE* node = toNode( aItem );
269 return !node || node->isSection;
270 }
271
272 // Section rows carry a name and a count in the file column. Without this the base class
273 // reports that a container has a value only in column 0, and the section label is never
274 // drawn, leaving the header as a bare checkbox.
275 bool HasContainerColumns( const wxDataViewItem& aItem ) const override { return true; }
276
277 unsigned int GetChildren( const wxDataViewItem& aItem, wxDataViewItemArray& aChildren ) const override
278 {
279 if( !aItem.IsOk() )
280 {
281 for( const std::unique_ptr<CHECKLIST_NODE>& section : m_sectionNodes )
282 aChildren.Add( wxDataViewItem( section.get() ) );
283
284 return aChildren.GetCount();
285 }
286
287 CHECKLIST_NODE* node = toNode( aItem );
288
289 if( node && node->isSection )
290 {
291 for( const std::unique_ptr<CHECKLIST_NODE>& child : node->children )
292 aChildren.Add( wxDataViewItem( child.get() ) );
293 }
294
295 return aChildren.GetCount();
296 }
297
298private:
299 CHECKLIST_NODE* toNode( const wxDataViewItem& aItem ) const
300 {
301 return static_cast<CHECKLIST_NODE*>( aItem.GetID() );
302 }
303
305 {
306 m_sectionNodes.clear();
307
308 for( const wxString& section : m_data.Sections() )
309 {
310 auto sectionNode = std::make_unique<CHECKLIST_NODE>();
311 sectionNode->isSection = true;
312 sectionNode->section = section;
313
314 for( size_t i = 0; i < m_data.Rows( section ).size(); ++i )
315 {
316 auto fileNode = std::make_unique<CHECKLIST_NODE>();
317 fileNode->section = section;
318 fileNode->index = i;
319 fileNode->parent = sectionNode.get();
320 sectionNode->children.push_back( std::move( fileNode ) );
321 }
322
323 m_sectionNodes.push_back( std::move( sectionNode ) );
324 }
325 }
326
328 std::vector<std::unique_ptr<CHECKLIST_NODE>> m_sectionNodes;
329};
330
331
332FILE_CHECKLIST_CTRL::FILE_CHECKLIST_CTRL( wxWindow* aParent, wxWindowID aId ) :
333 WX_DATAVIEWCTRL( aParent, aId, wxDefaultPosition, wxDefaultSize, wxDV_ROW_LINES ),
335{
336 AssociateModel( m_model.get() );
337
338 AppendToggleColumn( wxEmptyString, FILE_CHECKLIST_MODEL::COL_CHECK, wxDATAVIEW_CELL_ACTIVATABLE,
339 wxCOL_WIDTH_AUTOSIZE );
340 AppendIconTextColumn( _( "File" ), FILE_CHECKLIST_MODEL::COL_NAME, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE );
341 AppendTextColumn( _( "Status" ), FILE_CHECKLIST_MODEL::COL_STATUS, wxDATAVIEW_CELL_INERT, wxCOL_WIDTH_AUTOSIZE );
342
343 Bind( wxEVT_IDLE, &FILE_CHECKLIST_CTRL::onIdle, this );
344}
345
346
347void FILE_CHECKLIST_CTRL::DoSetToolTipText( const wxString& aTip )
348{
349 m_ownerTooltip = aTip;
350 m_shownTooltip = aTip;
351 wxDataViewCtrl::DoSetToolTipText( aTip );
352}
353
354
355// A row shows the file name alone, so two files with the same name in different directories
356// read the same. Hovering one shows the path that tells them apart.
357void FILE_CHECKLIST_CTRL::onIdle( wxIdleEvent& aEvent )
358{
359 aEvent.Skip();
360
361 wxString tip = m_ownerTooltip;
362 wxPoint screenPos = wxGetMousePosition();
363
364 if( IsShownOnScreen() && GetScreenRect().Contains( screenPos ) )
365 {
366 wxDataViewItem item;
367 wxDataViewColumn* column = nullptr;
368
369 HitTest( ScreenToClient( screenPos ), item, column );
370
371 wxString path = GetItemPath( item );
372
373 if( !path.IsEmpty() && path != wxFileName( path ).GetFullName() )
374 tip = path;
375 }
376
377 if( tip == m_shownTooltip )
378 return;
379
380 m_shownTooltip = tip;
381 wxDataViewCtrl::DoSetToolTipText( tip );
382}
383
384
385// Out of line because wxObjectDataPtr needs the complete model type to release it, and the
386// header only forward declares it.
390
391
392void FILE_CHECKLIST_CTRL::SetEntries( const std::vector<FILE_CHECKLIST_ENTRY>& aEntries )
393{
394 m_model->SetEntries( aEntries );
395 ExpandAll();
396}
397
398
400{
401 m_model->SetAllChecked( aChecked );
402}
403
404
405std::vector<wxString> FILE_CHECKLIST_CTRL::GetCheckedPaths() const
406{
407 return m_model->CheckedPaths();
408}
409
410
411wxString FILE_CHECKLIST_CTRL::GetItemPath( const wxDataViewItem& aItem ) const
412{
413 return m_model->PathFor( aItem );
414}
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
FILE_CHECKLIST_CTRL(wxWindow *aParent, wxWindowID aId=wxID_ANY)
void SetAllChecked(bool aChecked)
std::vector< wxString > GetCheckedPaths() const
wxString m_ownerTooltip
What the owner set, shown when no row has more to say.
wxString GetItemPath(const wxDataViewItem &aItem) const
Path of the file row for aItem, or empty for a section header or invalid item.
void onIdle(wxIdleEvent &aEvent)
wxDataViewCtrl sends no mouse moves, so the hovered row is read on idle.
wxString m_shownTooltip
What is on screen, so an unchanged hover is left alone.
void DoSetToolTipText(const wxString &aTip) override
WX_DATAVIEWCTRL drops tooltips for every data view it is the base of.
wxObjectDataPtr< FILE_CHECKLIST_MODEL > m_model
void SetEntries(const std::vector< FILE_CHECKLIST_ENTRY > &aEntries)
The rows and tick states behind FILE_CHECKLIST_CTRL.
std::vector< wxString > CheckedPaths() const
Paths of every ticked row, in the order they were added.
std::vector< wxString > m_sections
void SetEntries(const std::vector< FILE_CHECKLIST_ENTRY > &aEntries)
void SetSectionChecked(const wxString &aSection, bool aChecked)
std::map< wxString, std::vector< FILE_CHECKLIST_ENTRY > > m_rowsBySection
void SetChecked(const wxString &aPath, bool aChecked)
bool IsSectionChecked(const wxString &aSection) const
True when the section has rows and every one of them is ticked.
const std::vector< FILE_CHECKLIST_ENTRY > & Rows(const wxString &aSection) const
Rows under a section, in the order they were added. Empty for an unknown section.
bool IsContainer(const wxDataViewItem &aItem) const override
wxDataViewItem GetParent(const wxDataViewItem &aItem) const override
wxString PathFor(const wxDataViewItem &aItem) const
bool SetValue(const wxVariant &aVariant, const wxDataViewItem &aItem, unsigned int aCol) override
wxString GetColumnType(unsigned int aCol) const override
FILE_CHECKLIST_DATA m_data
std::vector< std::unique_ptr< CHECKLIST_NODE > > m_sectionNodes
std::vector< wxString > CheckedPaths() const
bool GetAttr(const wxDataViewItem &aItem, unsigned int aCol, wxDataViewItemAttr &aAttr) const override
void SetAllChecked(bool aChecked)
void SetEntries(const std::vector< FILE_CHECKLIST_ENTRY > &aEntries)
CHECKLIST_NODE * toNode(const wxDataViewItem &aItem) const
bool HasContainerColumns(const wxDataViewItem &aItem) const override
unsigned int GetChildren(const wxDataViewItem &aItem, wxDataViewItemArray &aChildren) const override
void GetValue(wxVariant &aVariant, const wxDataViewItem &aItem, unsigned int aCol) const override
unsigned int GetColumnCount() const override
Extension of the wxDataViewCtrl to include some helper functions for working with items.
bool AssociateModel(wxDataViewModel *aModel) override
Install a WX_ENSURE_VISIBLE_CANCELLER on aModel so deferred scrolls are dropped automatically when it...
static bool empty(const wxTextEntryBase *aCtrl)
#define _(s)
std::vector< std::unique_ptr< CHECKLIST_NODE > > children
CHECKLIST_NODE * parent
One file row in a FILE_CHECKLIST_CTRL.
wxString label
Shown in place of the path.
BITMAPS icon
wxColour statusColour
Text colour for the status, or the default when not set.
wxString status
Free text, e.g. "Modified".
bool checked
std::string path