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