KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_template_selector.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) 2012 Brian Sidebotham <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
22#include <bitmaps.h>
23#include <kiplatform/ui.h>
24#include <pgm_base.h>
28#include <widgets/ui_common.h>
29#include <algorithm>
30#include <wx_filename.h>
31#include <wx/dir.h>
32#include <wx/dirdlg.h>
33#include <wx/settings.h>
34#include <wx/bitmap.h>
35#include <wx/image.h>
36#include <wx/math.h>
37#include <wx/menu.h>
38#include <wx/textdlg.h>
39#include <wx/textfile.h>
40#include <wx/regex.h>
41#include <confirm.h>
44
45
46static const wxChar* traceTemplateSelector = wxT( "KICAD_TEMPLATE_SELECTOR" );
47
48
50 const wxString& aPath, const wxString& aTitle,
51 const wxBitmap& aIcon ) :
52 wxPanel( aParent, wxID_ANY ),
53 m_dialog( aDialog ),
54 m_templatePath( aPath )
55{
56 SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
57
58 wxBoxSizer* sizer = new wxBoxSizer( wxHORIZONTAL );
59
60 wxStaticBitmap* icon = new wxStaticBitmap( this, wxID_ANY, aIcon );
61 icon->SetMinSize( FromDIP( wxSize( 16, 16 ) ) );
62 sizer->Add( icon, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxTOP | wxBOTTOM, 6 );
63
64 wxStaticText* label = new wxStaticText( this, wxID_ANY, aTitle );
65 label->SetFont( KIUI::GetInfoFont( this ) );
66 sizer->Add( label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxTOP | wxBOTTOM, 6 );
67
68 SetSizer( sizer );
69 Layout();
70
71 Bind( wxEVT_LEFT_DOWN, &TEMPLATE_MRU_WIDGET::OnClick, this );
72 Bind( wxEVT_LEFT_DCLICK, &TEMPLATE_MRU_WIDGET::OnDoubleClick, this );
73 Bind( wxEVT_ENTER_WINDOW, &TEMPLATE_MRU_WIDGET::OnEnter, this );
74 Bind( wxEVT_LEAVE_WINDOW, &TEMPLATE_MRU_WIDGET::OnLeave, this );
75
76 icon->Bind( wxEVT_LEFT_DOWN, &TEMPLATE_MRU_WIDGET::OnClick, this );
77 icon->Bind( wxEVT_LEFT_DCLICK, &TEMPLATE_MRU_WIDGET::OnDoubleClick, this );
78 label->Bind( wxEVT_LEFT_DOWN, &TEMPLATE_MRU_WIDGET::OnClick, this );
79 label->Bind( wxEVT_LEFT_DCLICK, &TEMPLATE_MRU_WIDGET::OnDoubleClick, this );
80}
81
82
83void TEMPLATE_MRU_WIDGET::OnClick( wxMouseEvent& event )
84{
85 // Just select the template in the list without showing the preview pane
86 m_dialog->SelectTemplateByPath( m_templatePath, true );
87 event.Skip();
88}
89
90
91void TEMPLATE_MRU_WIDGET::OnDoubleClick( wxMouseEvent& event )
92{
93 m_dialog->SelectTemplateByPath( m_templatePath );
94 m_dialog->EndModal( wxID_OK );
95 event.Skip();
96}
97
98
99void TEMPLATE_MRU_WIDGET::OnEnter( wxMouseEvent& event )
100{
101 SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT ).ChangeLightness( 150 ) );
102 Refresh();
103 event.Skip();
104}
105
106
107void TEMPLATE_MRU_WIDGET::OnLeave( wxMouseEvent& event )
108{
109 SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
110 Refresh();
111 event.Skip();
112}
113
114
116 TEMPLATE_WIDGET_BASE( aParent )
117{
118 m_parent = aParent;
119 m_dialog = aDialog;
120 m_category = CATEGORY::SYSTEM;
121
122 // Set a small minimum size to allow the dialog to shrink
123 // The actual size will be determined by the parent sizer
124 SetMinSize( FromDIP( wxSize( 200, -1 ) ) );
125
126 m_bitmapIcon->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::OnMouse ), nullptr, this );
127 m_titleLabel->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::OnMouse ), nullptr, this );
128 m_descLabel->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::OnMouse ), nullptr, this );
129
130 Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::OnMouse ), nullptr, this );
131
132 m_bitmapIcon->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::onRightClick ), nullptr, this );
133 m_titleLabel->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::onRightClick ), nullptr, this );
134 m_descLabel->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::onRightClick ), nullptr, this );
135 Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::onRightClick ), nullptr, this );
136
137 m_bitmapIcon->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( TEMPLATE_WIDGET::OnDoubleClick ), nullptr, this );
138 m_titleLabel->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( TEMPLATE_WIDGET::OnDoubleClick ), nullptr, this );
139 m_descLabel->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( TEMPLATE_WIDGET::OnDoubleClick ), nullptr, this );
140 Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( TEMPLATE_WIDGET::OnDoubleClick ), nullptr, this );
141
142 // Set description text to use a smaller font
143 m_descLabel->SetFont( KIUI::GetInfoFont( this ) );
144
145#if wxCHECK_VERSION( 3, 3, 2 )
146 m_descLabel->SetWindowStyle( wxST_WRAP );
147#endif
148
149 // Bind size event for dynamic text wrapping
150 Bind( wxEVT_SIZE, &TEMPLATE_WIDGET::OnSize, this );
151
152 Unselect();
153
154 m_currTemplate = nullptr;
155}
156
157
159{
160 m_dialog->SetWidget( this );
161 SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT ) );
162 m_titleLabel->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHTTEXT ) );
163 m_descLabel->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHTTEXT ) );
164 m_selected = true;
165 Refresh();
166}
167
168
170{
171 SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT ) );
172 m_titleLabel->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHTTEXT ) );
173 m_descLabel->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHTTEXT ) );
174 m_selected = true;
175 Refresh();
176}
177
178
180{
181 SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
182 m_titleLabel->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNTEXT ) );
183 m_descLabel->SetForegroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNTEXT ) );
184 m_selected = false;
185 Refresh();
186}
187
188
190{
191 m_currTemplate = aTemplate;
192 m_titleLabel->SetFont( KIUI::GetInfoFont( this ).Bold() );
193 m_titleLabel->SetLabel( *aTemplate->GetTitle() );
194
195 // Generate bitmap bundle from template icon bitmap (64x64)
196 // so wxStaticBitmap can size itself to the closest match
197 const std::vector<int> c_bitmapSizes = { 48, 64, 96, 128 };
198
199 wxBitmapBundle bundle;
200 wxVector<wxBitmap> bitmaps;
201 wxBitmap* icon = aTemplate->GetIcon();
202
203 if( icon && icon->IsOk() )
204 bundle = *icon;
205 else
206 bundle = KiBitmapBundleDef( BITMAPS::icon_kicad, c_bitmapSizes[0] );
207
208 wxSize defSize = bundle.GetDefaultSize();
209
210 for( const int genSize : c_bitmapSizes )
211 {
212 double scale = std::min( (double) genSize / defSize.GetWidth(),
213 (double) genSize / defSize.GetHeight() );
214
215 wxSize scaledSize( wxRound( defSize.x * scale ),
216 wxRound( defSize.y * scale ) );
217
218 wxBitmap scaled = bundle.GetBitmap( scaledSize );
219 scaled.SetScaleFactor( 1.0 );
220
221 bitmaps.push_back( scaled );
222 }
223
224 m_bitmapIcon->SetBitmap( wxBitmapBundle::FromBitmaps( bitmaps ) );
225}
226
227
228void TEMPLATE_WIDGET::SetDescription( const wxString& aDescription )
229{
230 m_description = aDescription;
231
232 // Truncate long descriptions to approximately 3 lines worth
233 wxString displayDesc = aDescription;
234
235 if( displayDesc.Length() > 120 )
236 displayDesc = displayDesc.Left( 120 ) + wxS( "..." );
237
238 m_descLabel->SetLabel( displayDesc );
239}
240
241
242void TEMPLATE_WIDGET::OnSize( wxSizeEvent& event )
243{
244 event.Skip();
245
246 // wx 3.3.2+ can wrap automatically
247#if !wxCHECK_VERSION( 3, 3, 2 )
248 // Calculate available width for description text (widget width minus icon and padding)
249 int wrapWidth = GetClientSize().GetWidth() - 48 - 20; // 48px icon + 20px padding
250
251 if( wrapWidth > 100 )
252 {
253 // Recalculate the truncated description from the original stored description
254 // to avoid cumulative wrapping artifacts
255 wxString displayDesc = m_description;
256
257 if( displayDesc.Length() > 120 )
258 displayDesc = displayDesc.Left( 120 ) + wxS( "..." );
259
260 m_descLabel->SetLabel( displayDesc );
261 m_descLabel->Wrap( wrapWidth );
262 Layout();
263 }
264#endif
265}
266
267
268void TEMPLATE_WIDGET::OnMouse( wxMouseEvent& event )
269{
270 Select();
271 event.Skip();
272}
273
274
275void TEMPLATE_WIDGET::OnDoubleClick( wxMouseEvent& event )
276{
277 Select();
278 m_dialog->EndModal( wxID_OK );
279 event.Skip();
280}
281
282
283void TEMPLATE_WIDGET::onRightClick( wxMouseEvent& event )
284{
285 if( !m_currTemplate )
286 {
287 event.Skip();
288 return;
289 }
290
291 wxMenu menu;
292 menu.Append( wxID_EDIT,
293 m_category == CATEGORY::USER ? _( "Edit Template" ) : _( "Open Template (Read-Only)" ) );
294 menu.Append( wxID_OPEN, _( "Open Template Folder" ) );
295 menu.Append( wxID_COPY, _( "Duplicate Template" ) );
296
297 menu.Bind( wxEVT_COMMAND_MENU_SELECTED,
298 [this]( wxCommandEvent& evt )
299 {
300 if( evt.GetId() == wxID_EDIT )
301 onEditTemplate( evt );
302 else if( evt.GetId() == wxID_OPEN )
303 onOpenFolder( evt );
304 else if( evt.GetId() == wxID_COPY )
305 onDuplicateTemplate( evt );
306 } );
307
308 PopupMenu( &menu );
309}
310
311
312void TEMPLATE_WIDGET::onEditTemplate( wxCommandEvent& event )
313{
314 if( !m_currTemplate )
315 return;
316
317 wxFileName templatePath = m_currTemplate->GetHtmlFile();
318 templatePath.RemoveLastDir();
319
320 wxDir dir( templatePath.GetPath() );
321
322 if( !dir.IsOpened() )
323 {
324 DisplayErrorMessage( m_dialog, _( "Could not open template directory." ) );
325 return;
326 }
327
328 wxString filename;
329 bool found = dir.GetFirst( &filename, "*.kicad_pro", wxDIR_FILES );
330
331 if( !found )
332 {
333 DisplayErrorMessage( m_dialog, _( "No project file found in template directory." ) );
334 return;
335 }
336
337 wxFileName projectFile( templatePath.GetPath(), filename );
338
339 m_dialog->SetProjectToEdit( projectFile.GetFullPath() );
340
341 m_dialog->EndModal( wxID_APPLY );
342}
343
344
345void TEMPLATE_WIDGET::onOpenFolder( wxCommandEvent& event )
346{
347 if( !m_currTemplate )
348 return;
349
350 wxFileName templatePath = m_currTemplate->GetHtmlFile();
351 templatePath.RemoveLastDir();
352
353 if( !wxLaunchDefaultApplication( templatePath.GetPath() ) )
354 DisplayError( this, wxString::Format( _( "Failed to open '%s'." ), templatePath.GetPath() ) );
355}
356
357
358void TEMPLATE_WIDGET::onDuplicateTemplate( wxCommandEvent& event )
359{
360 if( !m_currTemplate )
361 return;
362
363 wxFileName templatePath = m_currTemplate->GetHtmlFile();
364 templatePath.RemoveLastDir();
365 wxString srcTemplatePath = templatePath.GetPath();
366 wxString srcTemplateName = m_currTemplate->GetPrjDirName();
367
368 wxTextEntryDialog nameDlg( m_dialog, _( "Enter name for the new template:" ), _( "Duplicate Template" ),
369 srcTemplateName + _( "_copy" ) );
370
371 if( nameDlg.ShowModal() != wxID_OK )
372 return;
373
374 wxString newTemplateName = nameDlg.GetValue();
375
376 if( newTemplateName.IsEmpty() )
377 {
378 DisplayErrorMessage( m_dialog, _( "Template name cannot be empty." ) );
379 return;
380 }
381
382 wxString userTemplatesPath = m_dialog->GetUserTemplatesPath();
383
384 if( userTemplatesPath.IsEmpty() )
385 {
386 DisplayErrorMessage( m_dialog, _( "Could not find user templates directory." ) );
387 return;
388 }
389
390 wxFileName destPath( userTemplatesPath, wxEmptyString );
391 destPath.AppendDir( newTemplateName );
392 wxString newTemplatePath = destPath.GetPath();
393
394 if( destPath.DirExists() )
395 {
396 DisplayErrorMessage( m_dialog, wxString::Format( _( "Directory '%s' already exists." ), newTemplatePath ) );
397 return;
398 }
399
400 if( !destPath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
401 {
402 DisplayErrorMessage( m_dialog, wxString::Format( _( "Could not create directory '%s'." ), newTemplatePath ) );
403 return;
404 }
405
406 wxDir sourceDir( srcTemplatePath );
407
408 if( !sourceDir.IsOpened() )
409 {
410 DisplayErrorMessage( m_dialog, _( "Could not open source template directory." ) );
411 return;
412 }
413
414 PROJECT_TREE_TRAVERSER traverser( nullptr, srcTemplatePath, srcTemplateName, newTemplatePath, newTemplateName );
415
416 sourceDir.Traverse( traverser );
417
418 if( !traverser.GetErrors().empty() )
419 {
420 DisplayErrorMessage( m_dialog, traverser.GetErrors() );
421 return;
422 }
423
424 wxFileName metaHtmlFile( newTemplatePath, "info.html" );
425 metaHtmlFile.AppendDir( "meta" );
426
427 if( metaHtmlFile.FileExists() )
428 {
429 wxTextFile htmlFile( metaHtmlFile.GetFullPath() );
430
431 if( htmlFile.Open() )
432 {
433 bool modified = false;
434
435 for( size_t i = 0; i < htmlFile.GetLineCount(); i++ )
436 {
437 wxString line = htmlFile.GetLine( i );
438
439 if( line.Contains( wxT( "<title>" ) ) && line.Contains( wxT( "</title>" ) ) )
440 {
441 int titleStart = line.Find( wxT( "<title>" ) );
442 int titleEnd = line.Find( wxT( "</title>" ) );
443
444 if( titleStart != wxNOT_FOUND && titleEnd != wxNOT_FOUND && titleEnd > titleStart )
445 {
446 wxString before = line.Left( titleStart + 7 );
447 wxString after = line.Mid( titleEnd );
448 line = before + newTemplateName + after;
449 htmlFile[i] = line;
450 modified = true;
451 }
452 }
453 }
454
455 if( modified )
456 htmlFile.Write();
457
458 htmlFile.Close();
459 }
460 }
461
462 // The file copy triggers OnFileSystemEvent which starts a refresh timer. If we show a
463 // modal info dialog here, the timer fires during the modal event loop and destroys this
464 // TEMPLATE_WIDGET while we're still on its call stack. Defer both the message and the
465 // refresh to run after the current event handler returns.
467
468 dlg->CallAfter(
469 [dlg, newTemplatePath]()
470 {
471 DisplayInfoMessage( dlg, wxString::Format( _( "Template duplicated successfully to '%s'." ),
472 newTemplatePath ) );
473 dlg->RefreshTemplateList();
474 } );
475}
476
477
478DIALOG_TEMPLATE_SELECTOR::DIALOG_TEMPLATE_SELECTOR( wxWindow* aParent, const wxPoint& aPos,
479 const wxSize& aSize, const wxString& aUserTemplatesPath,
480 const wxString& aSystemTemplatesPath,
481 const wxString& aDefaultTemplatesPath,
482 const std::vector<wxString>& aRecentTemplates,
483 const wxString& aBrowsedTemplatesPath ) :
484 DIALOG_TEMPLATE_SELECTOR_BASE( aParent, wxID_ANY, _( "Project Template Selector" ), aPos, aSize ),
486 m_selectedWidget( nullptr ),
487 m_selectedTemplate( nullptr ),
488 m_userTemplatesPath( aUserTemplatesPath ),
489 m_systemTemplatesPath( aSystemTemplatesPath ),
490 m_defaultTemplatesPath( aDefaultTemplatesPath ),
491 m_browsedTemplatesPath( aBrowsedTemplatesPath ),
492 m_recentTemplates( aRecentTemplates ),
493 m_searchTimer( this ),
494 m_refreshTimer( this ),
495 m_watcher( nullptr ),
496 m_webviewPanel( nullptr ),
497 m_loadingExternalHtml( false ),
499{
500 // Validate the persisted browsed path: drop it silently if the directory
501 // no longer exists so the dialog never points at a stale location.
502 if( !m_browsedTemplatesPath.IsEmpty() && !wxDirExists( m_browsedTemplatesPath ) )
504 // The base class now provides the UI structure via wxFormBuilder.
505 // Configure the scrolled windows.
506 m_scrolledMRU->SetScrollRate( 0, 25 );
507 m_scrolledTemplates->SetScrollRate( 0, 25 );
508 m_scrolledTemplates->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
509
510 m_scrolledTemplates->SetMinSize( FromDIP( wxSize( 200, 200 ) ) );
511
512 // Configure the search control
513 m_searchCtrl->ShowSearchButton( true );
514 m_searchCtrl->ShowCancelButton( true );
515
516 // The browsed-path label shrinks below its text width and ellipsizes, using the smaller
517 // info font; neither is expressible in the form so they are applied to the base widget here.
518 m_browsedPathLabel->SetFont( KIUI::GetInfoFont( this ) );
519 m_browsedPathLabel->SetMinSize( wxSize( 0, -1 ) );
520
521 Bind( wxEVT_TIMER, &DIALOG_TEMPLATE_SELECTOR::OnSearchTimer, this, m_searchTimer.GetId() );
522 Bind( wxEVT_TIMER, &DIALOG_TEMPLATE_SELECTOR::OnRefreshTimer, this, m_refreshTimer.GetId() );
523 Bind( wxEVT_FSWATCHER, &DIALOG_TEMPLATE_SELECTOR::OnFileSystemEvent, this );
524 Bind( wxEVT_SYS_COLOUR_CHANGED, &DIALOG_TEMPLATE_SELECTOR::OnSysColourChanged, this );
526
527 // Set initial filter based on saved setting
529
530 if( settings )
531 {
532 int filterChoice = settings->m_TemplateFilterChoice;
533
534 if( filterChoice >= 0 && filterChoice < static_cast<int>( m_filterChoice->GetCount() ) )
535 m_filterChoice->SetSelection( filterChoice );
536 else
537 m_filterChoice->SetSelection( 0 );
538 }
539 else
540 {
541 m_filterChoice->SetSelection( 0 );
542 }
543
544 // If the persisted browsed path was discarded (directory missing) avoid landing on the
545 // now-empty "Other Templates" filter.
546 if( m_browsedTemplatesPath.IsEmpty() && m_filterChoice->GetSelection() == 3 )
547 m_filterChoice->SetSelection( 0 );
548
550 BuildMRUList();
553
554 // Auto-select the most recently used template if available
555 if( !m_recentTemplates.empty() )
557
559
560 m_sdbSizerOK->SetDefault();
561
563}
564
565
567{
568 m_searchTimer.Stop();
569 m_refreshTimer.Stop();
570
571 if( m_watcher )
572 {
573 m_watcher->RemoveAll();
574 m_watcher->SetOwner( nullptr );
575 delete m_watcher;
576 m_watcher = nullptr;
577 }
578}
579
580
582{
583 if( m_splitter->IsSplit() )
584 return;
585
586 int sashPos = m_previewSashPos;
587
588 // Fall back to a third of the available width the first time the preview is shown
589 if( sashPos <= 0 )
590 {
591 int width = m_splitter->GetClientSize().GetWidth();
592 sashPos = width > 0 ? width / 3 : FromDIP( 300 );
593 }
594
595 m_splitter->SplitVertically( m_panelTemplates, m_panelPreview, sashPos );
596}
597
598
600{
601 m_state = aState;
602
603 switch( aState )
604 {
606 m_panelMRU->Show();
607 m_btnBack->Enable( false );
608
609 if( m_splitter->IsSplit() )
610 {
611 // Remember the sash so reselecting a template restores the user's layout
612 m_previewSashPos = m_splitter->GetSashPosition();
613 m_splitter->Unsplit( m_panelPreview );
614 }
615
616 break;
617
619 m_panelMRU->Hide();
620 m_btnBack->Enable( true );
622 break;
623
625 m_panelMRU->Show();
626 m_btnBack->Enable( true );
628 break;
629 }
630
631 Layout();
632}
633
634
636{
637 // Clear existing MRU widgets
638 for( TEMPLATE_MRU_WIDGET* widget : m_mruWidgets )
639 {
640 m_sizerMRU->Detach( widget );
641 widget->Destroy();
642 }
643
644 m_mruWidgets.clear();
645
646 for( const wxString& path : m_recentTemplates )
647 {
648 wxFileName templateDir;
649 templateDir.AssignDir( path );
650
651 if( !templateDir.DirExists() )
652 continue;
653
654 PROJECT_TEMPLATE templ( path );
655
656 wxString* title = templ.GetTitle();
657 wxBitmap* icon = templ.GetIcon();
658
659 wxBitmap scaledIcon;
660
661 if( icon && icon->IsOk() )
662 {
663 wxImage img = icon->ConvertToImage();
664 img.Rescale( 16, 16, wxIMAGE_QUALITY_HIGH );
665 scaledIcon = wxBitmap( img );
666 }
667 else
668 {
669 scaledIcon = KiBitmap( BITMAPS::icon_kicad );
670 wxImage img = scaledIcon.ConvertToImage();
671 img.Rescale( 16, 16, wxIMAGE_QUALITY_HIGH );
672 scaledIcon = wxBitmap( img );
673 }
674
675 wxString displayTitle = title ? *title : templateDir.GetDirs().Last();
676
677 TEMPLATE_MRU_WIDGET* mruWidget = new TEMPLATE_MRU_WIDGET( m_scrolledMRU, this, path, displayTitle,
678 scaledIcon );
679 m_sizerMRU->Add( mruWidget, 0, wxEXPAND | wxBOTTOM, 2 );
680 m_mruWidgets.push_back( mruWidget );
681 }
682
683 m_scrolledMRU->FitInside();
684 m_scrolledMRU->Layout();
685 m_panelMRU->Layout();
686}
687
688
690{
691 wxLogTrace( traceTemplateSelector, "BuildTemplateList() called" );
692
693 // Clear existing template widgets
694 for( TEMPLATE_WIDGET* widget : m_templateWidgets )
695 {
696 m_sizerTemplateList->Detach( widget );
697 widget->Destroy();
698 }
699
700 m_templateWidgets.clear();
701 m_templates.clear();
702
703 auto scanDirectory =
704 [this]( const wxString& aPath, TEMPLATE_WIDGET::CATEGORY aCategory )
705 {
706 if( aPath.IsEmpty() )
707 return;
708
709 wxDir dir;
710
711 if( !dir.Open( aPath ) )
712 return;
713
714 wxLogTrace( traceTemplateSelector, "Scanning directory: %s (category=%d)", aPath,
715 static_cast<int>( aCategory ) );
716
717 if( dir.HasSubDirs( "meta" ) )
718 {
719 auto templ = std::make_unique<PROJECT_TEMPLATE>( aPath );
720 wxFileName htmlFile = templ->GetHtmlFile();
721 wxString description = ExtractDescription( htmlFile );
722
724 widget->SetTemplate( templ.get() );
725 widget->SetDescription( description );
726 widget->SetCategory( aCategory );
727
728 m_templates.push_back( std::move( templ ) );
729 m_templateWidgets.push_back( widget );
730 }
731 else
732 {
733 wxString subName;
734 bool cont = dir.GetFirst( &subName, wxEmptyString, wxDIR_DIRS );
735
736 while( cont )
737 {
738 wxString subFull = aPath + wxFileName::GetPathSeparator() + subName;
739
740 // Only treat subdirectories that contain a meta directory as templates.
741 // Directories without meta (e.g. regular project directories the user
742 // placed in the template path) are silently skipped.
743 wxFileName metaTest;
744 metaTest.AssignDir( subFull );
745 metaTest.AppendDir( METADIR );
746
747 if( metaTest.DirExists() )
748 {
749 auto templ = std::make_unique<PROJECT_TEMPLATE>( subFull );
750 wxFileName htmlFile = templ->GetHtmlFile();
751 wxString description = templ->GetError().IsEmpty() ? ExtractDescription( htmlFile ) : templ->GetError();
752
753 TEMPLATE_WIDGET* widget =
755 widget->SetTemplate( templ.get() );
756 widget->SetDescription( description );
757 widget->SetCategory( aCategory );
758
759 m_templates.push_back( std::move( templ ) );
760 m_templateWidgets.push_back( widget );
761 }
762
763 cont = dir.GetNext( &subName );
764 }
765 }
766 };
767
768 scanDirectory( m_userTemplatesPath, TEMPLATE_WIDGET::CATEGORY::USER );
769 scanDirectory( m_systemTemplatesPath, TEMPLATE_WIDGET::CATEGORY::SYSTEM );
770
772 scanDirectory( m_browsedTemplatesPath, TEMPLATE_WIDGET::CATEGORY::BROWSED );
773
774 // The built-in "default" template lives in the stable default user templates path. It is
775 // always scanned so that the default remains available even when KICAD_USER_TEMPLATE_DIR
776 // points at a custom location. Treated as a built-in, not a user template.
777 scanDirectory( m_defaultTemplatesPath, TEMPLATE_WIDGET::CATEGORY::SYSTEM );
778
779 // Sort alphabetically with "Default" first
780 std::sort( m_templateWidgets.begin(), m_templateWidgets.end(),
782 {
783 wxString titleA = *a->GetTemplate()->GetTitle();
784 wxString titleB = *b->GetTemplate()->GetTitle();
785 int cmp = titleA.CmpNoCase( titleB );
786
787 if( cmp == 0 )
788 return false;
789
790 if( titleA.CmpNoCase( "default" ) == 0 )
791 return true;
792 else if( titleB.CmpNoCase( "default" ) == 0 )
793 return false;
794
795 return cmp < 0;
796 } );
797
798 for( TEMPLATE_WIDGET* widget : m_templateWidgets )
799 {
800 m_sizerTemplateList->Add( widget, 0, wxEXPAND | wxBOTTOM, 8 );
801 }
802
803 ApplyFilter();
804
805 // Force initial sizing of widgets after layout
806 CallAfter(
807 [this]()
808 {
809 wxSizeEvent evt( m_scrolledTemplates->GetSize() );
811 } );
812
813 wxLogTrace( traceTemplateSelector, "BuildTemplateList() found %zu templates", m_templateWidgets.size() );
814}
815
816
818{
819 int filterChoice = m_filterChoice->GetSelection();
820 wxString searchText = m_searchCtrl->GetValue().Lower();
821
822 for( TEMPLATE_WIDGET* widget : m_templateWidgets )
823 {
824 bool matchesFilter = true;
825 TEMPLATE_WIDGET::CATEGORY cat = widget->GetCategory();
826
827 // 0 = All, 1 = User, 2 = System, 3 = Browsed / Other
828 if( filterChoice == 1 && cat != TEMPLATE_WIDGET::CATEGORY::USER )
829 matchesFilter = false;
830 else if( filterChoice == 2 && cat != TEMPLATE_WIDGET::CATEGORY::SYSTEM )
831 matchesFilter = false;
832 else if( filterChoice == 3 && cat != TEMPLATE_WIDGET::CATEGORY::BROWSED )
833 matchesFilter = false;
834
835 bool matchesSearch = true;
836
837 if( !searchText.IsEmpty() )
838 {
839 wxString title = widget->GetTemplate()->GetTitle()->Lower();
840 wxString description = widget->GetDescription().Lower();
841
842 matchesSearch = title.Contains( searchText ) || description.Contains( searchText );
843 }
844
845 widget->Show( matchesFilter && matchesSearch );
846 }
847
848 m_scrolledTemplates->FitInside();
849 m_scrolledTemplates->Layout();
850 Layout();
851}
852
853
854void DIALOG_TEMPLATE_SELECTOR::OnSearchCtrl( wxCommandEvent& event )
855{
856 m_searchTimer.Stop();
857 m_searchTimer.StartOnce( 200 );
858}
859
860
862{
863 m_searchCtrl->Clear();
864 ApplyFilter();
865}
866
867
868void DIALOG_TEMPLATE_SELECTOR::OnFilterChanged( wxCommandEvent& event )
869{
871
872 if( settings )
873 settings->m_TemplateFilterChoice = m_filterChoice->GetSelection();
874
875 ApplyFilter();
876}
877
878
880{
881 ApplyFilter();
882}
883
884
886{
887 wxString selectedPath;
888
889 if( m_selectedWidget && m_selectedWidget->GetTemplate() )
890 {
891 wxFileName htmlFile = m_selectedWidget->GetTemplate()->GetHtmlFile();
892 htmlFile.RemoveLastDir();
893 selectedPath = htmlFile.GetPath();
894 }
895
896 // Clear pointers before destroying widgets to avoid dangling pointers
897 m_selectedWidget = nullptr;
898 m_selectedTemplate = nullptr;
899
901
902 if( !selectedPath.IsEmpty() )
903 SelectTemplateByPath( selectedPath );
904}
905
906
907void DIALOG_TEMPLATE_SELECTOR::OnBackClicked( wxCommandEvent& event )
908{
909 if( m_selectedWidget )
910 m_selectedWidget->Unselect();
911
912 m_selectedWidget = nullptr;
913 m_selectedTemplate = nullptr;
914
917}
918
919
921{
922 if( m_selectedWidget != nullptr && m_selectedWidget != aWidget )
923 m_selectedWidget->Unselect();
924
925 m_selectedWidget = aWidget;
926 m_selectedTemplate = aWidget ? aWidget->GetTemplate() : nullptr;
927
929
932}
933
934
936{
937 SelectTemplateByPath( aPath, false );
938}
939
940
941void DIALOG_TEMPLATE_SELECTOR::SelectTemplateByPath( const wxString& aPath, bool aKeepMRUVisible )
942{
943 for( TEMPLATE_WIDGET* widget : m_templateWidgets )
944 {
945 if( widget->GetTemplate() )
946 {
947 wxFileName htmlFile = widget->GetTemplate()->GetHtmlFile();
948 htmlFile.RemoveLastDir();
949
950 if( htmlFile.GetPath() == aPath )
951 {
952 if( aKeepMRUVisible )
953 {
954 // Select the widget but keep MRU visible, don't show preview
955 if( m_selectedWidget != nullptr && m_selectedWidget != widget )
956 m_selectedWidget->Unselect();
957
958 m_selectedWidget = widget;
959 m_selectedTemplate = widget->GetTemplate();
960
961 widget->SelectWithoutStateChange();
962
963 // Scroll the widget into view
964 int y = 0;
965 int scrollRate = 0;
966 widget->GetPosition( nullptr, &y );
967 m_scrolledTemplates->GetScrollPixelsPerUnit( nullptr, &scrollRate );
968
969 if( scrollRate > 0 )
970 m_scrolledTemplates->Scroll( -1, y / scrollRate );
971 }
972 else
973 {
974 widget->Select();
975 }
976
977 return;
978 }
979 }
980 }
981
982 wxLogTrace( traceTemplateSelector, "SelectTemplateByPath: template not found at %s", aPath );
983}
984
985
987{
988 if( m_webviewPanel )
989 return;
990
991 // Create the WEBVIEW_PANEL lazily to avoid WebKit JavaScript VM initialization issues
992 // when the dialog is opened from a coroutine context.
993 m_webviewPanel = new WEBVIEW_PANEL( m_webviewPlaceholder, wxID_ANY, wxDefaultPosition, wxDefaultSize,
994 wxTAB_TRAVERSAL );
995
996 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
997 sizer->Add( m_webviewPanel, 1, wxEXPAND );
998 m_webviewPlaceholder->SetSizer( sizer );
999 m_webviewPlaceholder->Layout();
1000
1001 m_webviewPanel->BindLoadedEvent();
1002
1003 if( m_webviewPanel->GetWebView() )
1004 {
1005 Bind( wxEVT_WEBVIEW_LOADED, &DIALOG_TEMPLATE_SELECTOR::OnWebViewLoaded, this,
1006 m_webviewPanel->GetWebView()->GetId() );
1007 }
1008}
1009
1010
1012{
1013 if( !aTemplate )
1014 return;
1015
1017
1018 wxFileName htmlFile = aTemplate->GetHtmlFile();
1019
1020 if( htmlFile.FileExists() && htmlFile.IsFileReadable() )
1021 {
1022 m_loadingExternalHtml = true;
1023 wxString url = wxFileName::FileNameToURL( htmlFile );
1024 m_webviewPanel->LoadURL( url );
1025 }
1026 else
1027 {
1028 m_loadingExternalHtml = false;
1029 wxString html = GetTemplateInfoHtml( *aTemplate->GetTitle(), KIPLATFORM::UI::IsDarkTheme() );
1030 m_webviewPanel->SetPage( html );
1031 }
1032}
1033
1034
1041
1042
1044{
1045 if( m_watcher )
1046 {
1047 m_watcher->RemoveAll();
1048 delete m_watcher;
1049 m_watcher = nullptr;
1050 }
1051
1053 m_watcher->SetOwner( this );
1054
1055 wxLogNull logNo;
1056
1057 auto watchDir =
1058 [this]( const wxString& aPath, const char* aLabel )
1059 {
1060 if( aPath.IsEmpty() )
1061 return;
1062
1063 wxFileName dir;
1064 dir.AssignDir( aPath );
1065
1066 if( dir.DirExists() )
1067 {
1068 m_watcher->Add( dir );
1069 wxLogTrace( traceTemplateSelector, "Watching %s templates: %s", aLabel, aPath );
1070 }
1071 };
1072
1073 watchDir( m_userTemplatesPath, "user" );
1074 watchDir( m_systemTemplatesPath, "system" );
1075 watchDir( m_defaultTemplatesPath, "default" );
1076
1077 if( !browsedPathIsDuplicate() )
1078 watchDir( m_browsedTemplatesPath, "browsed" );
1079}
1080
1081
1083{
1084 if( m_browsedTemplatesPath.IsEmpty() )
1085 return true;
1086
1087 auto sameDir =
1088 []( const wxString& a, const wxString& b )
1089 {
1090 if( a.IsEmpty() || b.IsEmpty() )
1091 return false;
1092
1093 wxFileName lhs;
1094 lhs.AssignDir( a );
1095 lhs.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
1096
1097 wxFileName rhs;
1098 rhs.AssignDir( b );
1099 rhs.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
1100
1101 return lhs.SameAs( rhs );
1102 };
1103
1106}
1107
1108
1109void DIALOG_TEMPLATE_SELECTOR::OnFileSystemEvent( wxFileSystemWatcherEvent& event )
1110{
1111 if( !m_watcher )
1112 return;
1113
1114 wxLogTrace( traceTemplateSelector, "File system event detected" );
1115
1116 // wxFileSystemWatcher may fire events from a worker thread on some platforms.
1117 // Use CallAfter to marshal timer operations to the main thread.
1118 CallAfter(
1119 [this]()
1120 {
1121 m_refreshTimer.Stop();
1122 m_refreshTimer.StartOnce( 500 );
1123 } );
1124}
1125
1126
1127void DIALOG_TEMPLATE_SELECTOR::OnSysColourChanged( wxSysColourChangedEvent& event )
1128{
1129 event.Skip();
1130
1131 // Update background colors for the templates panel
1132 m_scrolledTemplates->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
1133
1134 // Update all template widgets
1135 for( TEMPLATE_WIDGET* widget : m_templateWidgets )
1136 {
1137 if( widget->IsSelected() )
1138 widget->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_HIGHLIGHT ) );
1139 else
1140 widget->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
1141
1142 widget->Refresh();
1143 }
1144
1145 // Update all MRU widgets
1146 for( TEMPLATE_MRU_WIDGET* widget : m_mruWidgets )
1147 {
1148 widget->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) );
1149 widget->Refresh();
1150 }
1151
1152 m_scrolledTemplates->Refresh();
1153 m_scrolledMRU->Refresh();
1154}
1155
1156
1158{
1159 event.Skip();
1160
1161 // Get the client width of the scrolled window
1162 int clientWidth = m_scrolledTemplates->GetClientSize().GetWidth();
1163
1164 if( clientWidth <= 0 )
1165 return;
1166
1167 // Force each widget to rewrap its text at the new width
1168 for( TEMPLATE_WIDGET* widget : m_templateWidgets )
1169 {
1170 wxSizeEvent sizeEvt( wxSize( clientWidth, -1 ) );
1171 widget->GetEventHandler()->ProcessEvent( sizeEvt );
1172 }
1173
1174 m_scrolledTemplates->FitInside();
1175}
1176
1177
1179{
1180 event.Skip();
1181
1182 // Safety check - ensure webview panel is valid
1183 if( !m_webviewPanel || !m_webviewPanel->GetWebView() )
1184 return;
1185
1186 // Add style to external documets
1188 {
1189 wxString script = wxString::Format( wxS( R"(
1190( function()
1191{
1192 var style = document.createElement( 'style' );
1193 style.textContent = `
1194%s
1195 `;
1196 document.head.appendChild( style );
1197} )();
1198 )" ), GetCommonStyles() );
1199
1200#if !defined( __MINGW32__ ) // RunScriptAsync() is not supported on MINGW build
1201 if( m_webviewPanel->GetBackend() != wxWebViewBackendIE )
1202 m_webviewPanel->RunScriptAsync( script );
1203#endif
1204 }
1205}
1206
1207
1209{
1210 wxString selectedPath;
1211
1212 if( m_selectedWidget && m_selectedWidget->GetTemplate() )
1213 {
1214 wxFileName htmlFile = m_selectedWidget->GetTemplate()->GetHtmlFile();
1215 htmlFile.RemoveLastDir();
1216 selectedPath = htmlFile.GetPath();
1217 }
1218
1219 // Clear pointers before destroying widgets to avoid dangling pointers
1220 m_selectedWidget = nullptr;
1221 m_selectedTemplate = nullptr;
1222
1224
1225 if( !selectedPath.IsEmpty() )
1226 SelectTemplateByPath( selectedPath );
1227}
1228
1229
1230wxString DIALOG_TEMPLATE_SELECTOR::ExtractDescription( const wxFileName& aHtmlFile )
1231{
1232 if( !aHtmlFile.FileExists() || !aHtmlFile.IsFileReadable() )
1233 return wxEmptyString;
1234
1235 wxTextFile file( aHtmlFile.GetFullPath() );
1236
1237 if( !file.Open() )
1238 return wxEmptyString;
1239
1240 wxString content;
1241
1242 for( wxString line = file.GetFirstLine(); !file.Eof(); line = file.GetNextLine() )
1243 {
1244 content += line + wxT( " " );
1245 }
1246
1247 file.Close();
1248
1249 // Try to extract text from the meta description tag
1250 wxRegEx reMetaDesc( wxT( "<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']*)[\"']" ),
1251 wxRE_ICASE );
1252
1253 if( reMetaDesc.Matches( content ) )
1254 return reMetaDesc.GetMatch( content, 1 );
1255
1256 // Fallback to first paragraph tag (allowing nested HTML tags)
1257 wxRegEx reParagraph( wxT( "<p[^>]*>(.*?)</p>" ), wxRE_ICASE );
1258
1259 if( reParagraph.Matches( content ) )
1260 {
1261 wxString desc = reParagraph.GetMatch( content, 1 );
1262
1263 // Strip nested HTML tags
1264 wxRegEx reTags( wxT( "<[^>]*>" ) );
1265 reTags.ReplaceAll( &desc, wxT( " " ) );
1266
1267 // Decode common HTML entities
1268 desc.Replace( wxT( "&nbsp;" ), wxT( " " ) );
1269 desc.Replace( wxT( "&amp;" ), wxT( "&" ) );
1270 desc.Replace( wxT( "&lt;" ), wxT( "<" ) );
1271 desc.Replace( wxT( "&gt;" ), wxT( ">" ) );
1272 desc.Replace( wxT( "&quot;" ), wxT( "\"" ) );
1273
1274 // Normalize whitespace
1275 wxRegEx reWhitespace( wxT( "\\s+" ) );
1276 reWhitespace.ReplaceAll( &desc, wxT( " " ) );
1277
1278 desc = desc.Trim().Trim( false );
1279
1280 if( !desc.IsEmpty() )
1281 return desc;
1282 }
1283
1284 // Final fallback: extract text content from body, stripping all HTML tags
1285 wxRegEx reBody( wxT( "<body[^>]*>(.*)</body>" ), wxRE_ICASE );
1286
1287 if( reBody.Matches( content ) )
1288 {
1289 wxString body = reBody.GetMatch( content, 1 );
1290
1291 // Strip all HTML tags
1292 wxRegEx reTags( wxT( "<[^>]*>" ) );
1293 reTags.ReplaceAll( &body, wxT( " " ) );
1294
1295 // Decode common HTML entities
1296 body.Replace( wxT( "&nbsp;" ), wxT( " " ) );
1297 body.Replace( wxT( "&amp;" ), wxT( "&" ) );
1298 body.Replace( wxT( "&lt;" ), wxT( "<" ) );
1299 body.Replace( wxT( "&gt;" ), wxT( ">" ) );
1300 body.Replace( wxT( "&quot;" ), wxT( "\"" ) );
1301
1302 // Normalize whitespace
1303 wxRegEx reWhitespace( wxT( "\\s+" ) );
1304 reWhitespace.ReplaceAll( &body, wxT( " " ) );
1305
1306 body = body.Trim().Trim( false );
1307
1308 // Return up to 250 characters
1309 if( body.Length() > 250 )
1310 return body.Left( 250 ) + wxS( "..." );
1311
1312 return body;
1313 }
1314
1315 return wxEmptyString;
1316}
1317
1318
1323
1324
1325void DIALOG_TEMPLATE_SELECTOR::onBrowseClicked( wxCommandEvent& aEvent )
1326{
1327 wxString startPath = m_browsedTemplatesPath;
1328
1329 if( startPath.IsEmpty() )
1330 startPath = m_userTemplatesPath;
1331
1332 wxDirDialog dirDialog( this, _( "Select Templates Directory" ), startPath,
1333 wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST );
1334
1335 if( dirDialog.ShowModal() != wxID_OK )
1336 return;
1337
1338 wxFileName dirName = wxFileName::DirName( dirDialog.GetPath() );
1339 dirName.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
1340 m_browsedTemplatesPath = dirName.GetFullPath();
1341
1343
1344 // Make sure the user can actually see the templates that just got pulled in by
1345 // jumping to the Other Templates category whenever the filter is currently set
1346 // to something that would hide them.
1347 int filterChoice = m_filterChoice->GetSelection();
1348
1349 if( filterChoice != 0 && filterChoice != 3 )
1350 m_filterChoice->SetSelection( 3 );
1351
1354}
1355
1356
1358{
1359 if( m_browsedTemplatesPath.IsEmpty() )
1360 return;
1361
1362 m_browsedTemplatesPath.clear();
1364
1365 // If we were filtered to Other Templates, drop back to All so the list isn't empty.
1366 if( m_filterChoice->GetSelection() == 3 )
1367 m_filterChoice->SetSelection( 0 );
1368
1371}
1372
1373
1375{
1376 if( !m_browsedPathLabel )
1377 return;
1378
1379 if( m_browsedTemplatesPath.IsEmpty() )
1380 m_browsedPathLabel->SetLabel( _( "(no other directory)" ) );
1381 else
1383
1385 m_clearBrowseButton->Enable( !m_browsedTemplatesPath.IsEmpty() );
1386
1387 if( wxSizer* parentSizer = m_browsedPathLabel->GetContainingSizer() )
1388 parentSizer->Layout();
1389}
wxBitmapBundle KiBitmapBundleDef(BITMAPS aBitmap, int aDefHeight)
Constructs and returns a bitmap bundle for the given icon ID, with the default bitmap size being aDef...
Definition bitmap.cpp:112
wxBitmap KiBitmap(BITMAPS aBitmap, int aHeightTag)
Construct a wxBitmap from an image identifier Returns the image from the active theme if the image ha...
Definition bitmap.cpp:100
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
DIALOG_TEMPLATE_SELECTOR_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Project Template Selector"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(900, 600), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
wxString ExtractDescription(const wxFileName &aHtmlFile)
void SetState(DialogState aState)
void SelectTemplateByPath(const wxString &aPath)
void OnBackClicked(wxCommandEvent &event) override
void LoadTemplatePreview(PROJECT_TEMPLATE *aTemplate)
void OnSearchCtrl(wxCommandEvent &event) override
void OnFileSystemEvent(wxFileSystemWatcherEvent &event)
void onClearBrowsedClicked(wxCommandEvent &aEvent) override
bool browsedPathIsDuplicate() const
Returns true if the browsed path is empty or duplicates one of the standard template directories (aft...
void OnSearchCtrlCancel(wxCommandEvent &event) override
void OnWebViewLoaded(wxWebViewEvent &event)
void OnFilterChanged(wxCommandEvent &event) override
void onBrowseClicked(wxCommandEvent &aEvent) override
std::vector< TEMPLATE_WIDGET * > m_templateWidgets
void OnRefreshTimer(wxTimerEvent &event)
void OnScrolledTemplatesSize(wxSizeEvent &event)
void SetWidget(TEMPLATE_WIDGET *aWidget)
std::vector< TEMPLATE_MRU_WIDGET * > m_mruWidgets
void OnSysColourChanged(wxSysColourChangedEvent &event)
void OnSearchTimer(wxTimerEvent &event)
DIALOG_TEMPLATE_SELECTOR(wxWindow *aParent, const wxPoint &aPos, const wxSize &aSize, const wxString &aUserTemplatesPath, const wxString &aSystemTemplatesPath, const wxString &aDefaultTemplatesPath, const std::vector< wxString > &aRecentTemplates, const wxString &aBrowsedTemplatesPath=wxEmptyString)
std::vector< std::unique_ptr< PROJECT_TEMPLATE > > m_templates
std::vector< wxString > m_recentTemplates
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:124
A class which provides project template functionality.
wxBitmap * GetIcon()
Get the 64px^2 icon for the project template.
wxFileName GetHtmlFile()
Get the full Html filename for the project template.
wxString * GetTitle()
Get the title of the project (extracted from the html title tag)
Traverser class to duplicate/copy project or template files with proper renaming.
T * GetAppSettings(const char *aFilename)
Return a handle to the a given settings by type.
A widget displaying a recently used template with a small icon and title.
DIALOG_TEMPLATE_SELECTOR * m_dialog
void OnEnter(wxMouseEvent &event)
TEMPLATE_MRU_WIDGET(wxWindow *aParent, DIALOG_TEMPLATE_SELECTOR *aDialog, const wxString &aPath, const wxString &aTitle, const wxBitmap &aIcon)
void OnLeave(wxMouseEvent &event)
void OnClick(wxMouseEvent &event)
void OnDoubleClick(wxMouseEvent &event)
TEMPLATE_WIDGET_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxBORDER_THEME|wxTAB_TRAVERSAL, const wxString &name=wxEmptyString)
PROJECT_TEMPLATE * GetTemplate()
void OnMouse(wxMouseEvent &event)
void onOpenFolder(wxCommandEvent &event)
void SetCategory(CATEGORY aCategory)
void SetTemplate(PROJECT_TEMPLATE *aTemplate)
Set the project template for this widget, which will determine the icon and title associated with thi...
DIALOG_TEMPLATE_SELECTOR * m_dialog
void SetDescription(const wxString &aDescription)
void OnSize(wxSizeEvent &event)
void onEditTemplate(wxCommandEvent &event)
void OnDoubleClick(wxMouseEvent &event)
void onDuplicateTemplate(wxCommandEvent &event)
void onRightClick(wxMouseEvent &event)
TEMPLATE_WIDGET(wxWindow *aParent, DIALOG_TEMPLATE_SELECTOR *aDialog)
PROJECT_TEMPLATE * m_currTemplate
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition confirm.cpp:245
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:192
This file is part of the common library.
static const wxChar * traceTemplateSelector
#define _(s)
bool IsDarkTheme()
Determine if the desktop interface is currently using a dark theme or a light theme.
Definition wxgtk/ui.cpp:50
KICOMMON_API wxFont GetInfoFont(wxWindow *aWindow)
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
#define METADIR
A directory which contains information about the project template and does not get copied.
#define wxFileSystemWatcher
const int scale
wxString GetWelcomeHtml(bool aDarkMode)
wxString GetTemplateInfoHtml(const wxString &aTemplateName, bool aDarkMode)
std::string path
Functions to provide common constants and other functions to assist in making a consistent UI.
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:35