KiCad PCB EDA Suite
Loading...
Searching...
No Matches
project_tree_pane.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 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
5 * Copyright (C) 2012 Jean-Pierre Charras, jp.charras at wanadoo.fr
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, you may find one here:
20 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21 * or you may search the http://www.gnu.org website for the version 2 license,
22 * or you may write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 */
25
26#include <stack>
27#include <git/git_backend.h>
28
29#include <wx/regex.h>
30#include <wx/stdpaths.h>
31#include <wx/string.h>
32#include <wx/msgdlg.h>
33#include <wx/textdlg.h>
34#include <wx/timer.h>
35#include <wx/wupdlock.h>
36#include <wx/log.h>
37
38#include <advanced_config.h>
39#include <bitmaps.h>
40#include <bitmap_store.h>
41#include <confirm.h>
44#include <gestfich.h>
45#include <macros.h>
46#include <trace_helpers.h>
49#include <core/kicad_algo.h>
50#include <paths.h>
52#include <scoped_set_reset.h>
53#include <string_utils.h>
54#include <thread_pool.h>
55#include <launch_ext.h>
56#include <wx/dcclient.h>
57#include <wx/progdlg.h>
58#include <wx/settings.h>
59
75
77
78#include "project_tree_item.h"
79#include "project_tree.h"
80#include "pgm_kicad.h"
81#include "kicad_id.h"
82#include "kicad_manager_frame.h"
83
84#include "project_tree_pane.h"
85#include <widgets/kistatusbar.h>
86
87#include <kiplatform/io.h>
88#include <kiplatform/secrets.h>
89
90
91/* Note about the project tree build process:
92 * Building the project tree can be *very* long if there are a lot of subdirectories in the
93 * working directory. Unfortunately, this happens easily if the project file *.pro is in the
94 * user's home directory.
95 * So the tree project is built "on demand":
96 * First the tree is built from the current directory and shows files and subdirs.
97 * > First level subdirs trees are built (i.e subdirs contents are not read)
98 * > When expanding a subdir, each subdir contains is read, and the corresponding sub tree is
99 * populated on the fly.
100 */
101
102// list of files extensions listed in the tree project window
103// Add extensions in a compatible regex format to see others files types
104static const wxChar* s_allowedExtensionsToList[] =
105{
106 wxT( "^.*\\.pro$" ),
107 wxT( "^.*\\.kicad_pro$" ),
108 wxT( "^.*\\.pdf$" ),
109 wxT( "^.*\\.sch$" ), // Legacy Eeschema files
110 wxT( "^.*\\.kicad_sch$" ), // S-expr Eeschema files
111 wxT( "^[^$].*\\.brd$" ), // Legacy Pcbnew files
112 wxT( "^[^$].*\\.kicad_pcb$" ), // S format Pcbnew board files
113 wxT( "^[^$].*\\.kicad_dru$" ), // Design rule files
114 wxT( "^[^$].*\\.kicad_wks$" ), // S format kicad drawing sheet files
115 wxT( "^[^$].*\\.kicad_mod$" ), // S format kicad footprint files, currently not listed
116 wxT( "^.*\\.net$" ), // pcbnew netlist file
117 wxT( "^.*\\.cir$" ), // Spice netlist file
118 wxT( "^.*\\.lib$" ), // Legacy schematic library file
119 wxT( "^.*\\.kicad_sym$" ), // S-expr symbol libraries
120 wxT( "^.*\\.txt$" ), // Text files
121 wxT( "^.*\\.md$" ), // Markdown files
122 wxT( "^.*\\.pho$" ), // Gerber file (Old Kicad extension)
123 wxT( "^.*\\.gbr$" ), // Gerber file
124 wxT( "^.*\\.gbrjob$" ), // Gerber job file
125 wxT( "^.*\\.gb[alops]$" ), // Gerber back (or bottom) layer file (deprecated Protel ext)
126 wxT( "^.*\\.gt[alops]$" ), // Gerber front (or top) layer file (deprecated Protel ext)
127 wxT( "^.*\\.g[0-9]{1,2}$" ), // Gerber inner layer file (deprecated Protel ext)
128 wxT( "^.*\\.gm[0-9]{1,2}$" ), // Gerber mechanical layer file (deprecated Protel ext)
129 wxT( "^.*\\.gko$" ), // Gerber keepout layer file (deprecated Protel ext)
130 wxT( "^.*\\.odt$" ),
131 wxT( "^.*\\.htm$" ),
132 wxT( "^.*\\.html$" ),
133 wxT( "^.*\\.rpt$" ), // Report files
134 wxT( "^.*\\.csv$" ), // Report files in comma separated format
135 wxT( "^.*\\.pos$" ), // Footprint position files
136 wxT( "^.*\\.cmp$" ), // CvPcb cmp/footprint link files
137 wxT( "^.*\\.drl$" ), // Excellon drill files
138 wxT( "^.*\\.nc$" ), // Excellon NC drill files (alternate file ext)
139 wxT( "^.*\\.xnc$" ), // Excellon NC drill files (alternate file ext)
140 wxT( "^.*\\.svg$" ), // SVG print/plot files
141 wxT( "^.*\\.ps$" ), // PostScript plot files
142 wxT( "^.*\\.zip$" ), // Zip archive files
143 wxT( "^.*\\.kicad_jobset" ), // KiCad jobs file
144 nullptr // end of list
145};
146
147
153
154
156{
157 ID_PROJECT_TXTEDIT = 8700, // Start well above wxIDs
163
164 ID_GIT_INITIALIZE_PROJECT, // Initialize a new git repository in an existing project
165 ID_GIT_CLONE_PROJECT, // Clone a project from a remote repository
166 ID_GIT_COMMIT_PROJECT, // Commit all files in the project
167 ID_GIT_COMMIT_FILE, // Commit a single file
168 ID_GIT_SYNC_PROJECT, // Sync the project with the remote repository (pull and push -- same as Update)
169 ID_GIT_FETCH, // Fetch the remote repository (without merging -- this is the same as Refresh)
170 ID_GIT_PUSH, // Push the local repository to the remote repository
171 ID_GIT_PULL, // Pull the remote repository to the local repository
172 ID_GIT_RESOLVE_CONFLICT, // Present the user with a resolve conflicts dialog (ours/theirs/merge)
173 ID_GIT_REVERT_LOCAL, // Revert the local repository to the last commit
174 ID_GIT_COMPARE, // Compare the current project to a different branch or commit in the git repository
175 ID_GIT_REMOVE_VCS, // Toggle Git integration for this project (preference in kicad_prl)
176 ID_GIT_ADD_TO_INDEX, // Add a file to the git index
177 ID_GIT_REMOVE_FROM_INDEX, // Remove a file from the git index
178 ID_GIT_SWITCH_BRANCH, // Switch the local repository to a different branch
179 ID_GIT_SWITCH_QUICK1, // Switch the local repository to the first quick branch
180 ID_GIT_SWITCH_QUICK2, // Switch the local repository to the second quick branch
181 ID_GIT_SWITCH_QUICK3, // Switch the local repository to the third quick branch
182 ID_GIT_SWITCH_QUICK4, // Switch the local repository to the fourth quick branch
183 ID_GIT_SWITCH_QUICK5, // Switch the local repository to the fifth quick branch
184
186};
187
188
189BEGIN_EVENT_TABLE( PROJECT_TREE_PANE, wxSashLayoutWindow )
190 EVT_TREE_ITEM_ACTIVATED( ID_PROJECT_TREE, PROJECT_TREE_PANE::onSelect )
191 EVT_TREE_ITEM_EXPANDED( ID_PROJECT_TREE, PROJECT_TREE_PANE::onExpand )
192 EVT_TREE_ITEM_RIGHT_CLICK( ID_PROJECT_TREE, PROJECT_TREE_PANE::onRight )
199
215
217
218 EVT_IDLE( PROJECT_TREE_PANE::onIdle )
219 EVT_PAINT( PROJECT_TREE_PANE::onPaint )
220END_EVENT_TABLE()
221
222
223wxDECLARE_EVENT( UPDATE_ICONS, wxCommandEvent );
224
226 wxSashLayoutWindow( parent, ID_LEFT_FRAME, wxDefaultPosition, wxDefaultSize, wxNO_BORDER | wxTAB_TRAVERSAL )
227{
228 m_Parent = parent;
229 m_TreeProject = nullptr;
230 m_isRenaming = false;
231 m_selectedItem = nullptr;
232 m_watcherNeedReset = false;
233 m_gitLastError = GIT_ERROR_NONE;
234 m_watcher = nullptr;
235 m_gitIconsInitialized = false;
236
237 Bind( wxEVT_FSWATCHER,
238 wxFileSystemWatcherEventHandler( PROJECT_TREE_PANE::onFileSystemEvent ), this );
239
240 Bind( wxEVT_SYS_COLOUR_CHANGED,
241 wxSysColourChangedEventHandler( PROJECT_TREE_PANE::onThemeChanged ), this );
242
243 m_gitSyncTimer.SetOwner( this );
244 m_gitStatusTimer.SetOwner( this );
245 Bind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitSyncTimer ), this, m_gitSyncTimer.GetId() );
246 Bind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitStatusTimer ), this, m_gitStatusTimer.GetId() );
247 /*
248 * Filtering is now inverted: the filters are actually used to _enable_ support
249 * for a given file type.
250 */
251 for( int ii = 0; s_allowedExtensionsToList[ii] != nullptr; ii++ )
252 m_filters.emplace_back( s_allowedExtensionsToList[ii] );
253
254 m_filters.emplace_back( wxT( "^no KiCad files found" ) );
255
257}
258
259
261{
262 Unbind( wxEVT_FSWATCHER,
263 wxFileSystemWatcherEventHandler( PROJECT_TREE_PANE::onFileSystemEvent ), this );
264 Unbind( wxEVT_SYS_COLOUR_CHANGED,
265 wxSysColourChangedEventHandler( PROJECT_TREE_PANE::onThemeChanged ), this );
266
267 m_gitSyncTimer.Stop();
268 m_gitStatusTimer.Stop();
269 Unbind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitSyncTimer ), this, m_gitSyncTimer.GetId() );
270 Unbind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitStatusTimer ), this, m_gitStatusTimer.GetId() );
272}
273
274
276{
277 if( m_watcher )
278 {
279 m_watcher->RemoveAll();
280 m_watcher->SetOwner( nullptr );
281 delete m_watcher;
282 m_watcher = nullptr;
283 }
284}
285
286
288{
289 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
290
291 if( tree_data.size() != 1 )
292 return;
293
294 wxString prj_filename = tree_data[0]->GetFileName();
295
296 m_Parent->LoadProject( prj_filename );
297}
298
299
300void PROJECT_TREE_PANE::onOpenDirectory( wxCommandEvent& event )
301{
302 // Get the root directory name:
303 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
304
305 for( PROJECT_TREE_ITEM* item_data : tree_data )
306 {
307 // Ask for the new sub directory name
308 wxString curr_dir = item_data->GetDir();
309
310 if( curr_dir.IsEmpty() )
311 {
312 // Use project path if the tree view path was empty.
313 curr_dir = wxPathOnly( m_Parent->GetProjectFileName() );
314
315 // As a last resort use the user's documents folder.
316 if( curr_dir.IsEmpty() || !wxFileName::DirExists( curr_dir ) )
318
319 if( !curr_dir.IsEmpty() )
320 curr_dir += wxFileName::GetPathSeparator();
321 }
322
323 LaunchExternal( curr_dir );
324 }
325}
326
327
328void PROJECT_TREE_PANE::onCreateNewDirectory( wxCommandEvent& event )
329{
330 // Get the root directory name:
331 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
332
333 for( PROJECT_TREE_ITEM* item_data : tree_data )
334 {
335 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
336
337 // Ask for the new sub directory name
338 wxString curr_dir = item_data->GetDir();
339
340 if( curr_dir.IsEmpty() )
341 curr_dir = prj_dir;
342
343 wxString new_dir = wxGetTextFromUser( _( "Directory name:" ), _( "Create New Directory" ) );
344
345 if( new_dir.IsEmpty() )
346 return;
347
348 wxString full_dirname = curr_dir + wxFileName::GetPathSeparator() + new_dir;
349
350 if( !wxMkdir( full_dirname ) )
351 return;
352
353 addItemToProjectTree( full_dirname, item_data->GetId(), nullptr, false );
354 }
355}
356
357
359{
360 switch( type )
361 {
380 case TREE_FILE_TYPE::DRILL_NC: return "nc";
381 case TREE_FILE_TYPE::DRILL_XNC: return "xnc";
390
394 case TREE_FILE_TYPE::DIRECTORY: break;
395 }
396
397 return wxEmptyString;
398}
399
400
401std::vector<wxString> getProjects( const wxDir& dir )
402{
403 std::vector<wxString> projects;
404 wxString dir_filename;
405 bool haveFile = dir.GetFirst( &dir_filename );
406
407 while( haveFile )
408 {
409 wxFileName file( dir_filename );
410
411 if( file.GetExt() == FILEEXT::LegacyProjectFileExtension
412 || file.GetExt() == FILEEXT::ProjectFileExtension )
413 projects.push_back( file.GetName() );
414
415 haveFile = dir.GetNext( &dir_filename );
416 }
417
418 return projects;
419}
420
421
422
423
424
425wxTreeItemId PROJECT_TREE_PANE::addItemToProjectTree( const wxString& aName,
426 const wxTreeItemId& aParent,
427 std::vector<wxString>* aProjectNames,
428 bool aRecurse )
429{
431 wxFileName fn( aName );
432
433 if( KIPLATFORM::IO::IsFileHidden( aName ) )
434 return wxTreeItemId();
435
436 if( wxDirExists( aName ) )
437 {
439 }
440 else
441 {
442 // Filter
443 wxRegEx reg;
444 bool addFile = false;
445
446 for( const wxString& m_filter : m_filters )
447 {
448 wxCHECK2_MSG( reg.Compile( m_filter, wxRE_ICASE ), continue,
449 wxString::Format( "Regex %s failed to compile.", m_filter ) );
450
451 if( reg.Matches( aName ) )
452 {
453 addFile = true;
454 break;
455 }
456 }
457
458 if( !addFile )
459 return wxTreeItemId();
460
461 for( int i = static_cast<int>( TREE_FILE_TYPE::LEGACY_PROJECT );
462 i < static_cast<int>( TREE_FILE_TYPE::MAX ); i++ )
463 {
464 wxString ext = GetFileExt( (TREE_FILE_TYPE) i );
465
466 if( ext == wxT( "" ) )
467 continue;
468
469 if( reg.Compile( wxString::FromAscii( "^.*\\." ) + ext + wxString::FromAscii( "$" ), wxRE_ICASE )
470 && reg.Matches( aName ) )
471 {
472 type = (TREE_FILE_TYPE) i;
473 break;
474 }
475 }
476 }
477
478 wxString file = wxFileNameFromPath( aName );
479 wxFileName currfile( file );
480 wxFileName project( m_Parent->GetProjectFileName() );
481 bool showAllSchematics = m_TreeProject->GetGitRepo() != nullptr;
482
483 // Ignore legacy projects with the same name as the current project
484 if( ( type == TREE_FILE_TYPE::LEGACY_PROJECT )
485 && ( currfile.GetName().CmpNoCase( project.GetName() ) == 0 ) )
486 {
487 return wxTreeItemId();
488 }
489
490 if( !showAllSchematics && ( currfile.GetExt() == GetFileExt( TREE_FILE_TYPE::LEGACY_SCHEMATIC )
491 || currfile.GetExt() == GetFileExt( TREE_FILE_TYPE::SEXPR_SCHEMATIC ) ) )
492 {
493 if( aProjectNames )
494 {
495 if( !alg::contains( *aProjectNames, currfile.GetName() ) )
496 return wxTreeItemId();
497 }
498 else
499 {
500 PROJECT_TREE_ITEM* parentTreeItem = GetItemIdData( aParent );
501
502 if( !parentTreeItem )
503 return wxTreeItemId();
504
505 wxDir parentDir( parentTreeItem->GetDir() );
506 std::vector<wxString> projects = getProjects( parentDir );
507
508 if( !alg::contains( projects, currfile.GetName() ) )
509 return wxTreeItemId();
510 }
511 }
512
513 // also check to see if it is already there.
514 wxTreeItemIdValue cookie;
515 wxTreeItemId kid = m_TreeProject->GetFirstChild( aParent, cookie );
516
517 while( kid.IsOk() )
518 {
519 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
520
521 if( itemData && itemData->GetFileName() == aName )
522 return itemData->GetId(); // well, we would have added it, but it is already here!
523
524 kid = m_TreeProject->GetNextChild( aParent, cookie );
525 }
526
527 // Only show current files if both legacy and current files are present
532 {
533 kid = m_TreeProject->GetFirstChild( aParent, cookie );
534
535 while( kid.IsOk() )
536 {
537 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
538
539 if( itemData )
540 {
541 wxFileName fname( itemData->GetFileName() );
542
543 if( fname.GetName().CmpNoCase( currfile.GetName() ) == 0 )
544 {
545 switch( type )
546 {
548 if( itemData->GetType() == TREE_FILE_TYPE::JSON_PROJECT )
549 return wxTreeItemId();
550
551 break;
552
554 if( itemData->GetType() == TREE_FILE_TYPE::SEXPR_SCHEMATIC )
555 return wxTreeItemId();
556
557 break;
558
560 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_PROJECT )
561 m_TreeProject->Delete( kid );
562
563 break;
564
566 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_SCHEMATIC )
567 m_TreeProject->Delete( kid );
568
569 break;
570
571 default:
572 break;
573 }
574 }
575 }
576
577 kid = m_TreeProject->GetNextChild( aParent, cookie );
578 }
579 }
580
581 // Append the item (only appending the filename not the full path):
582 wxTreeItemId newItemId = m_TreeProject->AppendItem( aParent, file );
583 PROJECT_TREE_ITEM* data = new PROJECT_TREE_ITEM( type, aName, m_TreeProject );
584
585 m_TreeProject->SetItemData( newItemId, data );
586 data->SetState( 0 );
587
588 // Mark root files (files which have the same aName as the project)
589 wxString fileName = currfile.GetName().Lower();
590 wxString projName = project.GetName().Lower();
591
592 if( fileName == projName || fileName.StartsWith( projName + "-" ) )
593 data->SetRootFile( true );
594
595#ifndef __WINDOWS__
596 bool subdir_populated = false;
597#endif
598
599 // This section adds dirs and files found in the subdirs
600 // in this case AddFile is recursive, but for the first level only.
601 if( TREE_FILE_TYPE::DIRECTORY == type && aRecurse )
602 {
603 wxDir dir( aName );
604
605 if( dir.IsOpened() ) // protected dirs will not open properly.
606 {
607 std::vector<wxString> projects = getProjects( dir );
608 wxString dir_filename;
609 bool haveFile = dir.GetFirst( &dir_filename );
610
611 data->SetPopulated( true );
612
613#ifndef __WINDOWS__
614 subdir_populated = aRecurse;
615#endif
616
617 while( haveFile )
618 {
619 // Add name in tree, but do not recurse
620 wxString path = aName + wxFileName::GetPathSeparator() + dir_filename;
621 addItemToProjectTree( path, newItemId, &projects, false );
622
623 haveFile = dir.GetNext( &dir_filename );
624 }
625 }
626
627 // Sort filenames by alphabetic order
628 m_TreeProject->SortChildren( newItemId );
629 }
630
631#ifndef __WINDOWS__
632 if( subdir_populated )
633 m_watcherNeedReset = true;
634#endif
635
636 return newItemId;
637}
638
639
641{
642 std::lock_guard<std::mutex> lock1( m_gitStatusMutex );
643 std::lock_guard<std::mutex> lock2( m_gitTreeCacheMutex );
645
646 while( tp.get_tasks_running() )
647 {
648 tp.wait_for( std::chrono::milliseconds( 250 ) );
649 }
650
651 m_gitStatusTimer.Stop();
652 m_gitSyncTimer.Stop();
653 m_gitTreeCache.clear();
654 m_gitStatusIcons.clear();
655
656 wxString pro_dir = m_Parent->GetProjectFileName();
657
658 if( !m_TreeProject )
659 m_TreeProject = new PROJECT_TREE( this );
660 else
661 m_TreeProject->DeleteAllItems();
662
663 if( !pro_dir ) // This is empty from PROJECT_TREE_PANE constructor
664 return;
665
666 if( m_TreeProject->GetGitRepo() )
667 {
668 git_repository* repo = m_TreeProject->GetGitRepo();
670 m_TreeProject->SetGitRepo( nullptr );
671 m_gitIconsInitialized = false;
672 }
673
674 wxFileName fn = pro_dir;
675 bool prjReset = false;
676
677 if( !fn.IsOk() )
678 {
679 fn.Clear();
680 fn.SetPath( PATHS::GetDefaultUserProjectsPath() );
681 fn.SetName( NAMELESS_PROJECT );
683 prjReset = true;
684 }
685
686 bool prjOpened = fn.FileExists();
687
688 // Bind the git repository to the project tree (if it exists and not disabled for this project)
689 if( Pgm().GetCommonSettings()->m_Git.enableGit
690 && !Prj().GetLocalSettings().m_GitIntegrationDisabled )
691 {
692 m_TreeProject->SetGitRepo( KIGIT::PROJECT_GIT_UTILS::GetRepositoryForFile( fn.GetPath().c_str() ) );
693
694 if( m_TreeProject->GetGitRepo() )
695 {
696 m_TreeProject->GitCommon()->SetUsername( Prj().GetLocalSettings().m_GitRepoUsername );
697 m_TreeProject->GitCommon()->SetSSHKey( Prj().GetLocalSettings().m_GitSSHKey );
698 m_TreeProject->GitCommon()->UpdateCurrentBranchInfo();
699 }
700 }
701
702 // We may have opened a legacy project, in which case GetProjectFileName will return the
703 // name of the migrated (new format) file, which may not have been saved to disk yet.
704 if( !prjOpened && !prjReset )
705 {
707 prjOpened = fn.FileExists();
708
709 // Set the ext back so that in the tree view we see the (not-yet-saved) new file
711 }
712
713 // root tree:
714 m_root = m_TreeProject->AddRoot( fn.GetFullName(), static_cast<int>( TREE_FILE_TYPE::ROOT ),
715 static_cast<int>( TREE_FILE_TYPE::ROOT ) );
716 m_TreeProject->SetItemBold( m_root, true );
717
718 // The main project file is now a JSON file
721
722 m_TreeProject->SetItemData( m_root, data );
723
724 // Now adding all current files if available
725 if( prjOpened )
726 {
727 pro_dir = wxPathOnly( m_Parent->GetProjectFileName() );
728 wxDir dir( pro_dir );
729
730 if( dir.IsOpened() ) // protected dirs will not open, see "man opendir()"
731 {
732 std::vector<wxString> projects = getProjects( dir );
733 wxString filename;
734 bool haveFile = dir.GetFirst( &filename );
735
736 while( haveFile )
737 {
738 if( filename != fn.GetFullName() )
739 {
740 wxString name = dir.GetName() + wxFileName::GetPathSeparator() + filename;
741
742 // Add items living in the project directory, and populate the item
743 // if it is a directory (sub directories will be not populated)
744 addItemToProjectTree( name, m_root, &projects, true );
745 }
746
747 haveFile = dir.GetNext( &filename );
748 }
749 }
750 }
751 else
752 {
753 m_TreeProject->AppendItem( m_root, wxT( "Empty project" ) );
754 }
755
756 m_TreeProject->Expand( m_root );
757
758 // Sort filenames by alphabetic order
759 m_TreeProject->SortChildren( m_root );
760
761 CallAfter(
762 [this] ()
763 {
764 wxLogTrace( traceGit, "PROJECT_TREE_PANE::ReCreateTreePrj: starting timers" );
765 m_gitSyncTimer.Start( 100, wxTIMER_ONE_SHOT );
766 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
767 } );
768}
769
770
772{
773 if( !m_TreeProject->GetGitRepo() )
774 return false;
775
776 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
777 return statusHandler.HasChangedFiles();
778}
779
780
781void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event )
782{
783 wxTreeItemId curr_item = Event.GetItem();
784
785 // Ensure item is selected (Under Windows right click does not select the item)
786 m_TreeProject->SelectItem( curr_item );
787
788 std::vector<PROJECT_TREE_ITEM*> selection = GetSelectedData();
789 KIGIT_COMMON* git = m_TreeProject->GitCommon();
790 wxFileName prj_dir( Prj().GetProjectPath(), wxEmptyString );
791 wxFileName git_dir( git->GetGitRootDirectory(), wxEmptyString );
792 prj_dir.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_CASE | wxPATH_NORM_DOTS
793 | wxPATH_NORM_ENV_VARS | wxPATH_NORM_TILDE );
794 git_dir.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_CASE | wxPATH_NORM_DOTS
795 | wxPATH_NORM_ENV_VARS | wxPATH_NORM_TILDE );
796 wxString prj_name = prj_dir.GetFullPath();
797 wxString git_name = git_dir.GetFullPath();
798
799 bool can_switch_to_project = true;
800 bool can_create_new_directory = true;
801 bool can_open_this_directory = true;
802 bool can_edit = true;
803 bool can_rename = true;
804 bool can_delete = true;
805 bool run_jobs = false;
806
807 bool vcs_has_repo = m_TreeProject->GetGitRepo() != nullptr;
808 bool vcs_can_commit = hasChangedFiles();
809 bool vcs_can_init = !vcs_has_repo;
810 bool gitIntegrationDisabled = Prj().GetLocalSettings().m_GitIntegrationDisabled;
811 // Allow toggling if: repo exists in project, OR integration is disabled (to re-enable it)
812 bool vcs_can_remove = ( vcs_has_repo && git_name.StartsWith( prj_name ) ) || gitIntegrationDisabled;
813 bool vcs_can_fetch = vcs_has_repo && git->HasPushAndPullRemote();
814 bool vcs_can_push = vcs_can_fetch && git->HasLocalCommits();
815 bool vcs_can_pull = vcs_can_fetch;
816 bool vcs_can_switch = vcs_has_repo;
817 bool vcs_menu = Pgm().GetCommonSettings()->m_Git.enableGit;
818
819 // Check if the libgit2 library is available via backend
820 bool libgit_init = GetGitBackend() && GetGitBackend()->IsLibraryAvailable();
821
822 vcs_menu &= libgit_init;
823
824 if( selection.size() == 0 )
825 return;
826
827 // Remove things that don't make sense for multiple selections
828 if( selection.size() != 1 )
829 {
830 can_switch_to_project = false;
831 can_create_new_directory = false;
832 can_rename = false;
833 }
834
835 for( PROJECT_TREE_ITEM* item : selection )
836 {
837 // Check for empty project
838 if( !item )
839 {
840 can_switch_to_project = false;
841 can_edit = false;
842 can_rename = false;
843 continue;
844 }
845
846 can_delete = item->CanDelete();
847 can_rename = item->CanRename();
848
849 switch( item->GetType() )
850 {
853 can_rename = false;
854
855 if( item->GetId() == m_TreeProject->GetRootItem() )
856 {
857 can_switch_to_project = false;
858 }
859 else
860 {
861 can_create_new_directory = false;
862 can_open_this_directory = false;
863 }
864 break;
865
867 can_switch_to_project = false;
868 can_edit = false;
869 break;
870
873 can_edit = false;
874 can_switch_to_project = false;
875 can_create_new_directory = false;
876 can_open_this_directory = false;
877 break;
878
880 run_jobs = true;
881 can_edit = false;
883
887
888 default:
889 can_switch_to_project = false;
890 can_create_new_directory = false;
891 can_open_this_directory = false;
892
893 break;
894 }
895 }
896
897 wxMenu popup_menu;
898 wxString text;
899 wxString help_text;
900
901 if( can_switch_to_project )
902 {
903 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_SWITCH_TO_OTHER, _( "Switch to this Project" ),
904 _( "Close all editors, and switch to the selected project" ),
906 popup_menu.AppendSeparator();
907 }
908
909 if( can_create_new_directory )
910 {
911 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_NEWDIR, _( "New Directory..." ),
912 _( "Create a New Directory" ), KiBitmap( BITMAPS::directory ) );
913 }
914
915 if( can_open_this_directory )
916 {
917 if( selection.size() == 1 )
918 {
919#ifdef __APPLE__
920 text = _( "Reveal in Finder" );
921 help_text = _( "Reveals the directory in a Finder window" );
922#else
923 text = _( "Open Directory in File Explorer" );
924 help_text = _( "Opens the directory in the default system file manager" );
925#endif
926 }
927 else
928 {
929#ifdef __APPLE__
930 text = _( "Reveal in Finder" );
931 help_text = _( "Reveals the directories in a Finder window" );
932#else
933 text = _( "Open Directories in File Explorer" );
934 help_text = _( "Opens the directories in the default system file manager" );
935#endif
936 }
937
938 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_OPEN_DIR, text, help_text,
940 }
941
942 if( can_edit )
943 {
944 if( selection.size() == 1 )
945 help_text = _( "Open the file in a Text Editor" );
946 else
947 help_text = _( "Open files in a Text Editor" );
948
949 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_TXTEDIT, _( "Edit in a Text Editor" ), help_text,
951 }
952
953 if( run_jobs && selection.size() == 1 )
954 {
955 KIUI::AddMenuItem( &popup_menu, ID_JOBS_RUN, _( "Run Jobs" ), help_text,
957 }
958
959 if( can_rename )
960 {
961 if( selection.size() == 1 )
962 {
963 text = _( "Rename File..." );
964 help_text = _( "Rename file" );
965 }
966 else
967 {
968 text = _( "Rename Files..." );
969 help_text = _( "Rename files" );
970 }
971
972 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_RENAME, text, help_text,
974 }
975
976 if( can_delete )
977 {
978 if( selection.size() == 1 )
979 help_text = _( "Delete the file and its content" );
980 else
981 help_text = _( "Delete the files and their contents" );
982
983 if( can_switch_to_project
984 || can_create_new_directory
985 || can_open_this_directory
986 || can_edit
987 || can_rename )
988 {
989 popup_menu.AppendSeparator();
990 }
991
992#ifdef __WINDOWS__
993 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Delete" ), help_text,
995#else
996 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Move to Trash" ), help_text,
998#endif
999 }
1000
1001 if( vcs_menu )
1002 {
1003 wxMenu* vcs_submenu = new wxMenu();
1004 wxMenu* branch_submenu = new wxMenu();
1005 wxMenuItem* vcs_menuitem = nullptr;
1006
1007 vcs_menuitem = vcs_submenu->Append( ID_GIT_INITIALIZE_PROJECT,
1008 _( "Add Project to Version Control..." ),
1009 _( "Initialize a new repository" ) );
1010 vcs_menuitem->Enable( vcs_can_init );
1011
1012
1013 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_PROJECT, _( "Commit Project..." ),
1014 _( "Commit changes to the local repository" ) );
1015 vcs_menuitem->Enable( vcs_can_commit );
1016
1017 vcs_menuitem = vcs_submenu->Append( ID_GIT_PUSH, _( "Push" ),
1018 _( "Push committed local changes to remote repository" ) );
1019 vcs_menuitem->Enable( vcs_can_push );
1020
1021 vcs_menuitem = vcs_submenu->Append( ID_GIT_PULL, _( "Pull" ),
1022 _( "Pull changes from remote repository into local" ) );
1023 vcs_menuitem->Enable( vcs_can_pull );
1024
1025 vcs_submenu->AppendSeparator();
1026
1027 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_FILE, _( "Commit File..." ),
1028 _( "Commit changes to the local repository" ) );
1029 vcs_menuitem->Enable( vcs_can_commit );
1030
1031 vcs_submenu->AppendSeparator();
1032
1033 // vcs_menuitem = vcs_submenu->Append( ID_GIT_COMPARE, _( "Diff" ),
1034 // _( "Show changes between the repository and working tree" ) );
1035 // vcs_menuitem->Enable( vcs_can_diff );
1036
1037 std::vector<wxString> branchNames = m_TreeProject->GitCommon()->GetBranchNames();
1038
1039 // Skip the first one (that is the current branch)
1040 for( size_t ii = 1; ii < branchNames.size() && ii < 6; ++ii )
1041 {
1042 wxString msg = _( "Switch to branch " ) + branchNames[ii];
1043 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH + ii, branchNames[ii], msg );
1044 vcs_menuitem->Enable( vcs_can_switch );
1045 }
1046
1047 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Other..." ),
1048 _( "Switch to a different branch" ) );
1049 vcs_menuitem->Enable( vcs_can_switch );
1050
1051 vcs_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Switch to Branch" ), branch_submenu );
1052
1053 vcs_submenu->AppendSeparator();
1054
1055 if( gitIntegrationDisabled )
1056 {
1057 vcs_menuitem = vcs_submenu->Append( ID_GIT_REMOVE_VCS, _( "Enable Git Integration" ),
1058 _( "Re-enable Git integration for this project" ) );
1059 }
1060 else
1061 {
1062 vcs_menuitem = vcs_submenu->Append( ID_GIT_REMOVE_VCS, _( "Disable Git Integration" ),
1063 _( "Disable Git integration for this project" ) );
1064 }
1065
1066 vcs_menuitem->Enable( vcs_can_remove );
1067
1068 popup_menu.AppendSeparator();
1069 popup_menu.AppendSubMenu( vcs_submenu, _( "Version Control" ) );
1070 }
1071
1072 if( popup_menu.GetMenuItemCount() > 0 )
1073 PopupMenu( &popup_menu );
1074}
1075
1076
1078{
1079 wxString editorname = Pgm().GetTextEditor();
1080
1081 if( editorname.IsEmpty() )
1082 {
1083 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
1084 return;
1085 }
1086
1087 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1088
1089 for( PROJECT_TREE_ITEM* item_data : tree_data )
1090 {
1091 wxString fullFileName = item_data->GetFileName();
1092
1093 if( !fullFileName.IsEmpty() )
1094 {
1095 ExecuteFile( editorname, fullFileName.wc_str(), nullptr, false );
1096 }
1097 }
1098}
1099
1100
1101void PROJECT_TREE_PANE::onDeleteFile( wxCommandEvent& event )
1102{
1103 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1104
1105 for( PROJECT_TREE_ITEM* item_data : tree_data )
1106 item_data->Delete();
1107}
1108
1109
1110void PROJECT_TREE_PANE::onRenameFile( wxCommandEvent& event )
1111{
1112 wxTreeItemId curr_item = m_TreeProject->GetFocusedItem();
1113 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1114
1115 // XXX: Unnecessary?
1116 if( tree_data.size() != 1 )
1117 return;
1118
1119 wxString buffer = m_TreeProject->GetItemText( curr_item );
1120 wxString msg = wxString::Format( _( "Change filename: '%s'" ),
1121 tree_data[0]->GetFileName() );
1122 wxTextEntryDialog dlg( wxGetTopLevelParent( this ), msg, _( "Change filename" ), buffer );
1123
1124 if( dlg.ShowModal() != wxID_OK )
1125 return; // canceled by user
1126
1127 buffer = dlg.GetValue();
1128 buffer.Trim( true );
1129 buffer.Trim( false );
1130
1131 if( buffer.IsEmpty() )
1132 return; // empty file name not allowed
1133
1134 tree_data[0]->Rename( buffer, true );
1135 m_isRenaming = true;
1136}
1137
1138
1139void PROJECT_TREE_PANE::onSelect( wxTreeEvent& Event )
1140{
1141 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1142
1143 if( tree_data.size() != 1 )
1144 return;
1145
1146 // Bookmark the selected item but don't try and activate it until later. If we do it now,
1147 // there will be more events at least on Windows in this frame that will steal focus from
1148 // any newly launched windows
1149 m_selectedItem = tree_data[0];
1150}
1151
1152
1153void PROJECT_TREE_PANE::onIdle( wxIdleEvent& aEvent )
1154{
1155 // Idle executes once all other events finished processing. This makes it ideal to launch
1156 // a new window without starting Focus wars.
1157 if( m_watcherNeedReset )
1158 {
1159 m_selectedItem = nullptr;
1161 }
1162
1163 if( m_selectedItem != nullptr && m_TreeProject->GetRootItem().IsOk() )
1164 {
1165 // Make sure m_selectedItem still exists in the tree before activating it.
1166 std::vector<wxTreeItemId> validItemIds;
1167 m_TreeProject->GetItemsRecursively( m_TreeProject->GetRootItem(), validItemIds );
1168
1169 for( wxTreeItemId id : validItemIds )
1170 {
1171 if( GetItemIdData( id ) == m_selectedItem )
1172 {
1173 // Activate launches a window which may run the event loop on top of us and cause
1174 // onIdle to get called again, so be sure to null out m_selectedItem first.
1176 m_selectedItem = nullptr;
1177
1178 item->Activate( this );
1179 break;
1180 }
1181 }
1182 }
1183}
1184
1185
1186void PROJECT_TREE_PANE::onExpand( wxTreeEvent& Event )
1187{
1188 wxTreeItemId itemId = Event.GetItem();
1189 PROJECT_TREE_ITEM* tree_data = GetItemIdData( itemId );
1190
1191 if( !tree_data )
1192 return;
1193
1194 if( tree_data->GetType() != TREE_FILE_TYPE::DIRECTORY )
1195 return;
1196
1197 // explore list of non populated subdirs, and populate them
1198 wxTreeItemIdValue cookie;
1199 wxTreeItemId kid = m_TreeProject->GetFirstChild( itemId, cookie );
1200
1201#ifndef __WINDOWS__
1202 bool subdir_populated = false;
1203#endif
1204
1205 for( ; kid.IsOk(); kid = m_TreeProject->GetNextChild( itemId, cookie ) )
1206 {
1207 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1208
1209 if( !itemData || itemData->GetType() != TREE_FILE_TYPE::DIRECTORY )
1210 continue;
1211
1212 if( itemData->IsPopulated() )
1213 continue;
1214
1215 wxString fileName = itemData->GetFileName();
1216 wxDir dir( fileName );
1217
1218 if( dir.IsOpened() )
1219 {
1220 std::vector<wxString> projects = getProjects( dir );
1221 wxString dir_filename;
1222 bool haveFile = dir.GetFirst( &dir_filename );
1223
1224 while( haveFile )
1225 {
1226 // Add name to tree item, but do not recurse in subdirs:
1227 wxString name = fileName + wxFileName::GetPathSeparator() + dir_filename;
1228 addItemToProjectTree( name, kid, &projects, false );
1229
1230 haveFile = dir.GetNext( &dir_filename );
1231 }
1232
1233 itemData->SetPopulated( true ); // set state to populated
1234
1235#ifndef __WINDOWS__
1236 subdir_populated = true;
1237#endif
1238 }
1239
1240 // Sort filenames by alphabetic order
1241 m_TreeProject->SortChildren( kid );
1242 }
1243
1244#ifndef __WINDOWS__
1245 if( subdir_populated )
1246 m_watcherNeedReset = true;
1247#endif
1248}
1249
1250
1251std::vector<PROJECT_TREE_ITEM*> PROJECT_TREE_PANE::GetSelectedData()
1252{
1253 wxArrayTreeItemIds selection;
1254 std::vector<PROJECT_TREE_ITEM*> data;
1255
1256 m_TreeProject->GetSelections( selection );
1257
1258 for( const wxTreeItemId itemId : selection )
1259 {
1260 PROJECT_TREE_ITEM* item = GetItemIdData( itemId );
1261
1262 if( !item )
1263 {
1264 wxLogTrace( traceGit, wxS( "Null tree item returned for selection, dynamic_cast failed?" ) );
1265 continue;
1266 }
1267
1268 data.push_back( item );
1269 }
1270
1271 return data;
1272}
1273
1274
1276{
1277 return dynamic_cast<PROJECT_TREE_ITEM*>( m_TreeProject->GetItemData( aId ) );
1278}
1279
1280
1281wxTreeItemId PROJECT_TREE_PANE::findSubdirTreeItem( const wxString& aSubDir )
1282{
1283 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1284
1285 // If the subdir is the current working directory, return m_root
1286 // in main list:
1287 if( prj_dir == aSubDir )
1288 return m_root;
1289
1290 // The subdir is in the main tree or in a subdir: Locate it
1291 wxTreeItemIdValue cookie;
1292 wxTreeItemId root_id = m_root;
1293 std::stack<wxTreeItemId> subdirs_id;
1294
1295 wxTreeItemId child = m_TreeProject->GetFirstChild( root_id, cookie );
1296
1297 while( true )
1298 {
1299 if( ! child.IsOk() )
1300 {
1301 if( subdirs_id.empty() ) // all items were explored
1302 {
1303 root_id = child; // Not found: return an invalid wxTreeItemId
1304 break;
1305 }
1306 else
1307 {
1308 root_id = subdirs_id.top();
1309 subdirs_id.pop();
1310 child = m_TreeProject->GetFirstChild( root_id, cookie );
1311
1312 if( !child.IsOk() )
1313 continue;
1314 }
1315 }
1316
1317 PROJECT_TREE_ITEM* itemData = GetItemIdData( child );
1318
1319 if( itemData && ( itemData->GetType() == TREE_FILE_TYPE::DIRECTORY ) )
1320 {
1321 if( itemData->GetFileName() == aSubDir ) // Found!
1322 {
1323 root_id = child;
1324 break;
1325 }
1326
1327 // child is a subdir, push in list to explore it later
1328 if( itemData->IsPopulated() )
1329 subdirs_id.push( child );
1330 }
1331
1332 child = m_TreeProject->GetNextChild( root_id, cookie );
1333 }
1334
1335 return root_id;
1336}
1337
1338
1339void PROJECT_TREE_PANE::onFileSystemEvent( wxFileSystemWatcherEvent& event )
1340{
1341 // No need to process events when we're shutting down
1342 if( !m_watcher )
1343 return;
1344
1345 // Ignore events that are not file creation, deletion, renaming or modification because
1346 // they are not relevant to the project tree.
1347 if( !( event.GetChangeType() & ( wxFSW_EVENT_CREATE |
1348 wxFSW_EVENT_DELETE |
1349 wxFSW_EVENT_RENAME |
1350 wxFSW_EVENT_MODIFY ) ) )
1351 {
1352 return;
1353 }
1354
1355 const wxFileName& pathModified = event.GetPath();
1356 wxString subdir = pathModified.GetPath();
1357 wxString fn = pathModified.GetFullPath();
1358
1359 // Adjust directories to look like a file item (path and name).
1360 if( pathModified.GetFullName().IsEmpty() )
1361 {
1362 subdir = subdir.BeforeLast( '/' );
1363 fn = fn.BeforeLast( '/' );
1364 }
1365
1366 wxTreeItemId root_id = findSubdirTreeItem( subdir );
1367
1368 if( !root_id.IsOk() )
1369 return;
1370
1371 CallAfter( [this] ()
1372 {
1373 wxLogTrace( traceGit, wxS( "File system event detected, updating tree cache" ) );
1374 m_gitStatusTimer.Start( 1500, wxTIMER_ONE_SHOT );
1375 } );
1376
1377 wxTreeItemIdValue cookie; // dummy variable needed by GetFirstChild()
1378 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1379
1380 switch( event.GetChangeType() )
1381 {
1382 case wxFSW_EVENT_CREATE:
1383 {
1384 wxTreeItemId newitem = addItemToProjectTree( fn, root_id, nullptr, true );
1385
1386 // If we are in the process of renaming a file, select the new one
1387 // This is needed for MSW and OSX, since we don't get RENAME events from them, just a
1388 // pair of DELETE and CREATE events.
1389 if( m_isRenaming && newitem.IsOk() )
1390 {
1391 m_TreeProject->SelectItem( newitem );
1392 m_isRenaming = false;
1393 }
1394 }
1395 break;
1396
1397 case wxFSW_EVENT_DELETE:
1398 while( kid.IsOk() )
1399 {
1400 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1401
1402 if( itemData && itemData->GetFileName() == fn )
1403 {
1404 m_TreeProject->Delete( kid );
1405 return;
1406 }
1407 kid = m_TreeProject->GetNextChild( root_id, cookie );
1408 }
1409 break;
1410
1411 case wxFSW_EVENT_RENAME :
1412 {
1413 const wxFileName& newpath = event.GetNewPath();
1414 wxString newdir = newpath.GetPath();
1415 wxString newfn = newpath.GetFullPath();
1416
1417 while( kid.IsOk() )
1418 {
1419 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1420
1421 if( itemData && itemData->GetFileName() == fn )
1422 {
1423 m_TreeProject->Delete( kid );
1424 break;
1425 }
1426
1427 kid = m_TreeProject->GetNextChild( root_id, cookie );
1428 }
1429
1430 // Add the new item only if it is not the current project file (root item).
1431 // Remember: this code is called by a wxFileSystemWatcherEvent event, and not always
1432 // called after an actual file rename, and the cleanup code does not explore the
1433 // root item, because it cannot be renamed by the user. Also, ensure the new file
1434 // actually exists on the file system before it is readded. On Linux, moving a file
1435 // to the trash can cause the same path to be returned in both the old and new paths
1436 // of the event, even though the file isn't there anymore.
1437 PROJECT_TREE_ITEM* rootData = GetItemIdData( root_id );
1438
1439 if( rootData && newpath.Exists() && ( newfn != rootData->GetFileName() ) )
1440 {
1441 wxTreeItemId newroot_id = findSubdirTreeItem( newdir );
1442 wxTreeItemId newitem = addItemToProjectTree( newfn, newroot_id, nullptr, true );
1443
1444 // If the item exists, select it
1445 if( newitem.IsOk() )
1446 m_TreeProject->SelectItem( newitem );
1447 }
1448
1449 m_isRenaming = false;
1450 }
1451 break;
1452
1453 default:
1454 return;
1455 }
1456
1457 // Sort filenames by alphabetic order
1458 m_TreeProject->SortChildren( root_id );
1459}
1460
1461
1463{
1464 m_watcherNeedReset = false;
1465
1466 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1467
1468#if defined( _WIN32 )
1469 KISTATUSBAR* statusBar = static_cast<KISTATUSBAR*>( m_Parent->GetStatusBar() );
1470
1471 if( KIPLATFORM::ENV::IsNetworkPath( prj_dir ) )
1472 {
1473 // Due to a combination of a bug in SAMBA sending bad change event IDs and wxWidgets
1474 // choosing to fault on an invalid event ID instead of sanely ignoring them we need to
1475 // avoid spawning a filewatcher. Unfortunately this punishes corporate environments with
1476 // Windows Server shares :/
1477 m_Parent->m_FileWatcherInfo = _( "Network path: not monitoring folder changes" );
1478 statusBar->SetEllipsedTextField( m_Parent->m_FileWatcherInfo, 1 );
1479 return;
1480 }
1481 else
1482 {
1483 m_Parent->m_FileWatcherInfo = _( "Local path: monitoring folder changes" );
1484 statusBar->SetEllipsedTextField( m_Parent->m_FileWatcherInfo, 1 );
1485 }
1486#endif
1487
1488 // Prepare file watcher:
1489 if( m_watcher )
1490 {
1491 m_watcher->RemoveAll();
1492 }
1493 else
1494 {
1495 // Create a wxWidgets log handler to catch errors during watcher creation
1496 // We need to to this because we cannot get error codes from the wxFileSystemWatcher
1497 // constructor. On Linux, if inotify cannot be initialized (usually due to resource limits),
1498 // wxWidgets will throw a system error and then, we throw another error below, trying to
1499 // add paths to a null watcher. We skip this by installing a temporary log handler that
1500 // catches errors during watcher creation and aborts if any error is detected.
1501 class WatcherLogHandler : public wxLog
1502 {
1503 public:
1504 explicit WatcherLogHandler( bool* err ) :
1505 m_err( err )
1506 {
1507 if( m_err )
1508 *m_err = false;
1509 }
1510
1511 protected:
1512 void DoLogTextAtLevel( wxLogLevel level, const wxString& text ) override
1513 {
1514 if( m_err && ( level == wxLOG_Error || level == wxLOG_FatalError ) )
1515 *m_err = true;
1516 }
1517
1518 private:
1519 bool* m_err;
1520 };
1521
1522 bool watcherHasError = false;
1523 WatcherLogHandler tmpLog( &watcherHasError );
1524 wxLog* oldLog = wxLog::SetActiveTarget( &tmpLog );
1525
1527 m_watcher->SetOwner( this );
1528
1529 // Restore previous log handler
1530 wxLog::SetActiveTarget( oldLog );
1531
1532 if( watcherHasError )
1533 {
1534 return;
1535 }
1536 }
1537
1538 // We can see wxString under a debugger, not a wxFileName
1539 wxFileName fn;
1540 fn.AssignDir( prj_dir );
1541 fn.DontFollowLink();
1542
1543 // Add directories which should be monitored.
1544 // under windows, we add the curr dir and all subdirs
1545 // under unix, we add only the curr dir and the populated subdirs
1546 // see http://docs.wxwidgets.org/trunk/classwx_file_system_watcher.htm
1547 // under unix, the file watcher needs more work to be efficient
1548 // moreover, under wxWidgets 2.9.4, AddTree does not work properly.
1549 {
1550 wxLogNull logNo; // avoid log messages
1551#ifdef __WINDOWS__
1552 if( ! m_watcher->AddTree( fn ) )
1553 {
1554 wxLogTrace( tracePathsAndFiles, "%s: failed to add '%s'\n", __func__,
1555 TO_UTF8( fn.GetFullPath() ) );
1556 return;
1557 }
1558 }
1559#else
1560 if( !m_watcher->Add( fn ) )
1561 {
1562 wxLogTrace( tracePathsAndFiles, "%s: failed to add '%s'\n", __func__,
1563 TO_UTF8( fn.GetFullPath() ) );
1564 return;
1565 }
1566 }
1567
1568 if( m_TreeProject->IsEmpty() )
1569 return;
1570
1571 // Add subdirs
1572 wxTreeItemIdValue cookie;
1573 wxTreeItemId root_id = m_root;
1574
1575 std::stack < wxTreeItemId > subdirs_id;
1576
1577 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1578 int total_watch_count = 0;
1579
1580 while( total_watch_count < ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
1581 {
1582 if( !kid.IsOk() )
1583 {
1584 if( subdirs_id.empty() ) // all items were explored
1585 {
1586 break;
1587 }
1588 else
1589 {
1590 root_id = subdirs_id.top();
1591 subdirs_id.pop();
1592 kid = m_TreeProject->GetFirstChild( root_id, cookie );
1593
1594 if( !kid.IsOk() )
1595 continue;
1596 }
1597 }
1598
1599 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1600
1601 if( itemData && itemData->GetType() == TREE_FILE_TYPE::DIRECTORY )
1602 {
1603 // we can see wxString under a debugger, not a wxFileName
1604 const wxString& path = itemData->GetFileName();
1605
1606 wxLogTrace( tracePathsAndFiles, "%s: add '%s'\n", __func__, TO_UTF8( path ) );
1607
1608 if( wxFileName::IsDirReadable( path ) ) // linux whines about watching protected dir
1609 {
1610 {
1611 // Silence OS errors that come from the watcher
1612 wxLogNull silence;
1613 fn.AssignDir( path );
1614 m_watcher->Add( fn );
1615 total_watch_count++;
1616 }
1617
1618 // if kid is a subdir, push in list to explore it later
1619 if( itemData->IsPopulated() && m_TreeProject->GetChildrenCount( kid ) )
1620 subdirs_id.push( kid );
1621 }
1622 }
1623
1624 kid = m_TreeProject->GetNextChild( root_id, cookie );
1625 }
1626
1627 if( total_watch_count >= ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
1628 wxLogTrace( tracePathsAndFiles, "%s: too many directories to watch\n", __func__ );
1629#endif
1630
1631#if defined(DEBUG) && 1
1632 wxArrayString paths;
1633 m_watcher->GetWatchedPaths( &paths );
1634 wxLogTrace( tracePathsAndFiles, "%s: watched paths:", __func__ );
1635
1636 for( unsigned ii = 0; ii < paths.GetCount(); ii++ )
1637 wxLogTrace( tracePathsAndFiles, " %s\n", TO_UTF8( paths[ii] ) );
1638#endif
1639 }
1640
1641
1643{
1644 // Make sure we don't try to inspect the tree after we've deleted its items.
1646
1647 m_TreeProject->DeleteAllItems();
1648
1649 // Remove the git repository when the project is unloaded
1650 if( m_TreeProject->GetGitRepo() )
1651 {
1652 m_TreeProject->GitCommon()->SetCancelled( true );
1653
1654 // We need to lock the mutex to ensure that no other thread is using the git repository
1655 std::unique_lock<std::mutex> lock( m_TreeProject->GitCommon()->m_gitActionMutex, std::try_to_lock );
1656
1657 if( !lock.owns_lock() )
1658 {
1659 // Block until any in-flight Git actions complete, showing a pulsing progress dialog
1660 {
1661 wxProgressDialog progress( _( "Please wait" ), _( "Waiting for Git operations to finish..." ),
1662 100, this, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_SMOOTH );
1663
1664 // Keep trying to acquire the lock, pulsing the dialog every 100 ms
1665 while ( !lock.try_lock() )
1666 {
1667 progress.Pulse();
1668 std::this_thread::sleep_for( std::chrono::milliseconds(100) );
1669 // allow UI events to process so dialog remains responsive
1670 wxYield();
1671 }
1672 }
1673 }
1674
1675 git_repository* repo = m_TreeProject->GetGitRepo();
1677 m_TreeProject->SetGitRepo( nullptr );
1678 }
1679}
1680
1681
1682void PROJECT_TREE_PANE::onThemeChanged( wxSysColourChangedEvent &aEvent )
1683{
1685 m_TreeProject->LoadIcons();
1686 m_TreeProject->Refresh();
1687
1688 aEvent.Skip();
1689}
1690
1691
1692void PROJECT_TREE_PANE::onPaint( wxPaintEvent& event )
1693{
1694 wxRect rect( wxPoint( 0, 0 ), GetClientSize() );
1695 wxPaintDC dc( this );
1696
1697 dc.SetBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_FRAMEBK ) );
1698 dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_ACTIVEBORDER ), 1 ) );
1699
1700 dc.DrawLine( rect.GetLeft(), rect.GetTop(), rect.GetLeft(), rect.GetBottom() );
1701 dc.DrawLine( rect.GetRight(), rect.GetTop(), rect.GetRight(), rect.GetBottom() );
1702}
1703
1704
1705void KICAD_MANAGER_FRAME::OnChangeWatchedPaths( wxCommandEvent& aEvent )
1706{
1707 m_leftWin->FileWatcherReset();
1708}
1709
1710
1711void PROJECT_TREE_PANE::onGitInitializeProject( wxCommandEvent& aEvent )
1712{
1713 PROJECT_TREE_ITEM* tree_data = GetItemIdData( m_TreeProject->GetRootItem() );
1714
1715 wxString dir = tree_data->GetDir();
1716
1717 if( dir.empty() )
1718 {
1719 wxLogError( "Failed to initialize git project: project directory is empty." );
1720 return;
1721 }
1722
1723 GIT_INIT_HANDLER initHandler( m_TreeProject->GitCommon() );
1724 wxWindow* topLevelParent = wxGetTopLevelParent( this );
1725
1726 if( initHandler.IsRepository( dir ) )
1727 {
1728 DisplayInfoMessage( topLevelParent,
1729 _( "The selected directory is already a Git project." ) );
1730 return;
1731 }
1732
1733 InitResult result = initHandler.InitializeRepository( dir );
1735 {
1736 DisplayErrorMessage( m_parent, _( "Failed to initialize Git project." ),
1737 initHandler.GetErrorString() );
1738 return;
1739 }
1740
1741 m_gitLastError = GIT_ERROR_NONE;
1742
1743 DIALOG_GIT_REPOSITORY dlg( topLevelParent, initHandler.GetRepo() );
1744 dlg.SetTitle( _( "Set default remote" ) );
1745 dlg.SetSkipButtonLabel( _( "Skip" ) );
1746
1747 if( dlg.ShowModal() != wxID_OK )
1748 return;
1749
1750 // Set up the remote
1751 RemoteConfig remoteConfig;
1752 remoteConfig.url = dlg.GetRepoURL();
1753 remoteConfig.username = dlg.GetUsername();
1754 remoteConfig.password = dlg.GetPassword();
1755 remoteConfig.sshKey = dlg.GetRepoSSHPath();
1756 remoteConfig.connType = dlg.GetRepoType();
1757
1758 if( !initHandler.SetupRemote( remoteConfig ) )
1759 {
1760 DisplayErrorMessage( m_parent, _( "Failed to set default remote." ),
1761 initHandler.GetErrorString() );
1762 return;
1763 }
1764
1765 m_gitLastError = GIT_ERROR_NONE;
1766
1767 GIT_PULL_HANDLER handler( m_TreeProject->GitCommon() );
1768 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetch Remote" ), 1, PR_NO_ABORT ) );
1769 handler.PerformFetch();
1770
1774
1778 Prj().GetLocalSettings().m_GitRepoType = "https";
1779 else
1780 Prj().GetLocalSettings().m_GitRepoType = "local";
1781}
1782
1783
1784void PROJECT_TREE_PANE::onGitCompare( wxCommandEvent& aEvent )
1785{
1786
1787}
1788
1789
1790void PROJECT_TREE_PANE::onGitPullProject( wxCommandEvent& aEvent )
1791{
1792 git_repository* repo = m_TreeProject->GetGitRepo();
1793
1794 if( !repo )
1795 return;
1796
1797 GIT_PULL_HANDLER handler( m_TreeProject->GitCommon() );
1798
1799 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetch Remote" ), 1,
1800 PR_NO_ABORT ) );
1801
1802 if( handler.PerformPull() < PullResult::Success )
1803 {
1804 wxString errorMessage = handler.GetErrorString();
1805
1806 DisplayErrorMessage( m_parent, _( "Failed to pull project" ), errorMessage );
1807 }
1808
1809 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1810}
1811
1812
1813void PROJECT_TREE_PANE::onGitPushProject( wxCommandEvent& aEvent )
1814{
1815 git_repository* repo = m_TreeProject->GetGitRepo();
1816
1817 if( !repo )
1818 return;
1819
1820 GIT_PUSH_HANDLER handler( m_TreeProject->GitCommon() );
1821
1822 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetch Remote" ), 1,
1823 PR_NO_ABORT ) );
1824
1825 if( handler.PerformPush() != PushResult::Success )
1826 {
1827 wxString errorMessage = handler.GetErrorString();
1828
1829 DisplayErrorMessage( m_parent, _( "Failed to push project" ), errorMessage );
1830 }
1831
1832 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1833}
1834
1835
1836void PROJECT_TREE_PANE::onGitSwitchBranch( wxCommandEvent& aEvent )
1837{
1838 if( !m_TreeProject->GetGitRepo() )
1839 return;
1840
1841 GIT_BRANCH_HANDLER branchHandler( m_TreeProject->GitCommon() );
1842 wxString branchName;
1843
1844 if( aEvent.GetId() == ID_GIT_SWITCH_BRANCH )
1845 {
1846 DIALOG_GIT_SWITCH dlg( wxGetTopLevelParent( this ), m_TreeProject->GetGitRepo() );
1847
1848 int retval = dlg.ShowModal();
1849 branchName = dlg.GetBranchName();
1850
1851 if( retval == wxID_ADD )
1852 KIGIT::PROJECT_GIT_UTILS::CreateBranch( m_TreeProject->GetGitRepo(), branchName );
1853 else if( retval != wxID_OK )
1854 return;
1855 }
1856 else
1857 {
1858 std::vector<wxString> branches = m_TreeProject->GitCommon()->GetBranchNames();
1859 int branchIndex = aEvent.GetId() - ID_GIT_SWITCH_BRANCH;
1860
1861 if( branchIndex < 0 || static_cast<size_t>( branchIndex ) >= branches.size() )
1862 return;
1863
1864 branchName = branches[branchIndex];
1865 }
1866
1867 wxLogTrace( traceGit, wxS( "onGitSwitchBranch: Switching to branch '%s'" ), branchName );
1868 if( branchHandler.SwitchToBranch( branchName ) != BranchResult::Success )
1869 {
1870 DisplayError( m_parent, branchHandler.GetErrorString() );
1871 }
1872}
1873
1874
1875void PROJECT_TREE_PANE::onGitRemoveVCS( wxCommandEvent& aEvent )
1876{
1877 PROJECT_LOCAL_SETTINGS& localSettings = Prj().GetLocalSettings();
1878
1879 // Toggle the Git integration disabled preference
1880 localSettings.m_GitIntegrationDisabled = !localSettings.m_GitIntegrationDisabled;
1881
1882 wxLogTrace( traceGit, wxS( "onGitRemoveVCS: Git integration %s" ),
1883 localSettings.m_GitIntegrationDisabled ? wxS( "disabled" ) : wxS( "enabled" ) );
1884
1885 if( localSettings.m_GitIntegrationDisabled )
1886 {
1887 // Disabling Git integration - clear the repo reference and item states
1888 m_TreeProject->SetGitRepo( nullptr );
1889 m_gitIconsInitialized = false;
1890
1891 // Clear all item states to remove git status icons
1892 std::stack<wxTreeItemId> items;
1893 items.push( m_TreeProject->GetRootItem() );
1894
1895 while( !items.empty() )
1896 {
1897 wxTreeItemId current = items.top();
1898 items.pop();
1899
1900 m_TreeProject->SetItemState( current, wxTREE_ITEMSTATE_NONE );
1901
1902 wxTreeItemIdValue cookie;
1903 wxTreeItemId child = m_TreeProject->GetFirstChild( current, cookie );
1904
1905 while( child.IsOk() )
1906 {
1907 items.push( child );
1908 child = m_TreeProject->GetNextChild( current, cookie );
1909 }
1910 }
1911 }
1912 else
1913 {
1914 // Re-enabling Git integration - try to find and connect to the repository
1915 wxFileName fn( Prj().GetProjectPath() );
1916 m_TreeProject->SetGitRepo( KIGIT::PROJECT_GIT_UTILS::GetRepositoryForFile( fn.GetPath().c_str() ) );
1917
1918 if( m_TreeProject->GetGitRepo() )
1919 {
1920 m_TreeProject->GitCommon()->SetUsername( localSettings.m_GitRepoUsername );
1921 m_TreeProject->GitCommon()->SetSSHKey( localSettings.m_GitSSHKey );
1922 }
1923 }
1924
1925 // Save the preference to the project local settings file
1926 localSettings.SaveToFile( Prj().GetProjectPath() );
1927}
1928
1929
1931{
1932 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Updating git status icons" ) );
1933 std::unique_lock<std::mutex> lock( m_gitStatusMutex, std::try_to_lock );
1934
1935 if( !lock.owns_lock() )
1936 {
1937 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Failed to acquire lock for git status icon update" ) );
1938 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1939 return;
1940 }
1941
1942 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
1943 {
1944 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Git is disabled or tree control is null" ) );
1945 return;
1946 }
1947
1948 std::stack<wxTreeItemId> items;
1949 items.push(m_TreeProject->GetRootItem());
1950
1951 while( !items.empty() )
1952 {
1953 wxTreeItemId current = items.top();
1954 items.pop();
1955
1956 if( m_TreeProject->ItemHasChildren( current ) )
1957 {
1958 wxTreeItemIdValue cookie;
1959 wxTreeItemId child = m_TreeProject->GetFirstChild( current, cookie );
1960
1961 while( child.IsOk() )
1962 {
1963 items.push( child );
1964
1965 if( auto it = m_gitStatusIcons.find( child ); it != m_gitStatusIcons.end() )
1966 {
1967 m_TreeProject->SetItemState( child, static_cast<int>( it->second ) );
1968 }
1969
1970 child = m_TreeProject->GetNextChild( current, cookie );
1971 }
1972 }
1973 }
1974
1975 if( !m_gitCurrentBranchName.empty() )
1976 {
1977 wxTreeItemId kid = m_TreeProject->GetRootItem();
1978 PROJECT_TREE_ITEM* rootItem = GetItemIdData( kid );
1979
1980 if( rootItem )
1981 {
1982 wxString filename = wxFileNameFromPath( rootItem->GetFileName() );
1983 m_TreeProject->SetItemText( kid, filename + " [" + m_gitCurrentBranchName + "]" );
1984 m_gitIconsInitialized = true;
1985 }
1986 }
1987
1988 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Git status icons updated" ) );
1989}
1990
1991
1993{
1994 wxLogTrace( traceGit, wxS( "updateTreeCache: Updating tree cache" ) );
1995
1996 std::unique_lock<std::mutex> lock( m_gitTreeCacheMutex, std::try_to_lock );
1997
1998 if( !lock.owns_lock() )
1999 {
2000 wxLogTrace( traceGit, wxS( "updateTreeCache: Failed to acquire lock for tree cache update" ) );
2001 return;
2002 }
2003
2004 if( !m_TreeProject )
2005 {
2006 wxLogTrace( traceGit, wxS( "updateTreeCache: Tree control is null" ) );
2007 return;
2008 }
2009
2010 wxTreeItemId kid = m_TreeProject->GetRootItem();
2011
2012 if( !kid.IsOk() )
2013 return;
2014
2015 // Collect a map to easily set the state of each item
2016 m_gitTreeCache.clear();
2017 std::stack<wxTreeItemId> items;
2018 items.push( kid );
2019
2020 while( !items.empty() )
2021 {
2022 kid = items.top();
2023 items.pop();
2024
2025 PROJECT_TREE_ITEM* nextItem = GetItemIdData( kid );
2026
2027 if( !nextItem )
2028 continue;
2029
2030 wxString gitAbsPath = nextItem->GetFileName();
2031#ifdef _WIN32
2032 gitAbsPath.Replace( wxS( "\\" ), wxS( "/" ) );
2033#endif
2034 m_gitTreeCache[gitAbsPath] = kid;
2035
2036 wxTreeItemIdValue cookie;
2037 wxTreeItemId child = m_TreeProject->GetFirstChild( kid, cookie );
2038
2039 while( child.IsOk() )
2040 {
2041 items.push( child );
2042 child = m_TreeProject->GetNextChild( kid, cookie );
2043 }
2044 }
2045}
2046
2047
2049{
2050 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Updating git status icons" ) );
2051#if defined( _WIN32 )
2053
2054 if( refresh != 0
2055 && KIPLATFORM::ENV::IsNetworkPath( wxPathOnly( m_Parent->GetProjectFileName() ) ) )
2056 {
2057 // Need to treat windows network paths special here until we get the samba bug fixed
2058 // https://github.com/wxWidgets/wxWidgets/issues/18953
2059 CallAfter(
2060 [this, refresh]()
2061 {
2062 m_gitStatusTimer.Start( refresh, wxTIMER_ONE_SHOT );
2063 } );
2064 }
2065
2066#endif
2067
2068 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
2069 return;
2070
2071 std::unique_lock<std::mutex> lock1( m_gitStatusMutex, std::try_to_lock );
2072 std::unique_lock<std::mutex> lock2( m_gitTreeCacheMutex, std::try_to_lock );
2073
2074 if( !lock1.owns_lock() || !lock2.owns_lock() )
2075 {
2076 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Failed to acquire locks for git status icon update" ) );
2077 return;
2078 }
2079
2080 if( !m_TreeProject->GetGitRepo() )
2081 {
2082 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: No git repository found" ) );
2083 return;
2084 }
2085
2086 // Acquire the git action mutex to synchronize with EmptyTreePrj() shutdown.
2087 // This ensures the repository isn't freed while we're using it.
2088 std::unique_lock<std::mutex> gitLock( m_TreeProject->GitCommon()->m_gitActionMutex, std::try_to_lock );
2089
2090 if( !gitLock.owns_lock() )
2091 {
2092 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Failed to acquire git action mutex" ) );
2093 return;
2094 }
2095
2096 // Check if cancellation was requested (e.g., during shutdown)
2097 if( m_TreeProject->GitCommon()->IsCancelled() )
2098 {
2099 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Cancelled" ) );
2100 return;
2101 }
2102
2103 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
2104
2105 // Set up pathspec for project files
2106 wxFileName rootFilename( Prj().GetProjectFullName() );
2107 wxString repoWorkDir = statusHandler.GetWorkingDirectory();
2108
2109 wxFileName relative = rootFilename;
2110 relative.MakeRelativeTo( repoWorkDir );
2111 wxString pathspecStr = relative.GetPath( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR );
2112
2113#ifdef _WIN32
2114 pathspecStr.Replace( wxS( "\\" ), wxS( "/" ) );
2115#endif
2116
2117 // Get file status
2118 auto fileStatusMap = statusHandler.GetFileStatus( pathspecStr );
2119 auto [localChanges, remoteChanges] = m_TreeProject->GitCommon()->GetDifferentFiles();
2120 statusHandler.UpdateRemoteStatus( localChanges, remoteChanges, fileStatusMap );
2121
2122 bool updated = false;
2123
2124 // Update status icons based on file status
2125 for( const auto& [absPath, fileStatus] : fileStatusMap )
2126 {
2127 auto iter = m_gitTreeCache.find( absPath );
2128 if( iter == m_gitTreeCache.end() )
2129 {
2130 wxLogTrace( traceGit, wxS( "File '%s' not found in tree cache" ), absPath );
2131 continue;
2132 }
2133
2134 auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second, fileStatus.status );
2135 if( inserted || it->second != fileStatus.status )
2136 updated = true;
2137 it->second = fileStatus.status;
2138 }
2139
2140 // Get the current branch name
2142
2143 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Updated git status icons" ) );
2144
2145 // Update UI if icons changed
2146 if( updated || !m_gitIconsInitialized )
2147 {
2148 CallAfter(
2149 [this]()
2150 {
2152 } );
2153 }
2154}
2155
2156
2157void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
2158{
2159 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
2160
2161 git_repository* repo = m_TreeProject->GetGitRepo();
2162
2163 if( repo == nullptr )
2164 {
2165 wxMessageBox( _( "The selected directory is not a Git project." ) );
2166 return;
2167 }
2168
2169 // Get git configuration
2170 GIT_CONFIG_HANDLER configHandler( m_TreeProject->GitCommon() );
2171 GitUserConfig userConfig = configHandler.GetUserConfig();
2172
2173 // Collect modified files in the repository
2174 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
2175 auto fileStatusMap = statusHandler.GetFileStatus();
2176
2177 std::map<wxString, int> modifiedFiles;
2178 std::set<wxString> selected_files;
2179
2180 for( PROJECT_TREE_ITEM* item : tree_data )
2181 {
2182 if( item->GetType() != TREE_FILE_TYPE::DIRECTORY )
2183 selected_files.emplace( item->GetFileName() );
2184 }
2185
2186 wxString repoWorkDir = statusHandler.GetWorkingDirectory();
2187
2188 for( const auto& [absPath, fileStatus] : fileStatusMap )
2189 {
2190 // Skip current, conflicted, or ignored files
2191 if( fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT
2193 || fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_IGNORED )
2194 {
2195 continue;
2196 }
2197
2198 wxFileName fn( absPath );
2199
2200 // Convert to relative path for the modifiedFiles map
2201 wxString relativePath = absPath;
2202 if( relativePath.StartsWith( repoWorkDir ) )
2203 {
2204 relativePath = relativePath.Mid( repoWorkDir.length() );
2205#ifdef _WIN32
2206 relativePath.Replace( wxS( "\\" ), wxS( "/" ) );
2207#endif
2208 }
2209
2210 // Do not commit files outside the project directory
2211 wxString projectPath = Prj().GetProjectPath();
2212 if( !absPath.StartsWith( projectPath ) )
2213 continue;
2214
2215 // Skip lock files
2216 if( fn.GetExt().CmpNoCase( FILEEXT::LockFileExtension ) == 0 )
2217 continue;
2218
2219 // Skip autosave, lock, and backup files
2220 if( fn.GetName().StartsWith( FILEEXT::LockFilePrefix )
2221 || fn.GetName().EndsWith( FILEEXT::BackupFileSuffix ) )
2222 {
2223 continue;
2224 }
2225
2226 // Skip archived project backups
2227 if( fn.GetPath().Contains( Prj().GetProjectName() + wxT( "-backups" ) ) )
2228 continue;
2229
2230 if( aEvent.GetId() == ID_GIT_COMMIT_PROJECT )
2231 {
2232 modifiedFiles.emplace( relativePath, fileStatus.gitStatus );
2233 }
2234 else if( selected_files.count( absPath ) )
2235 {
2236 modifiedFiles.emplace( relativePath, fileStatus.gitStatus );
2237 }
2238 }
2239
2240 // Create a commit dialog
2241 DIALOG_GIT_COMMIT dlg( wxGetTopLevelParent( this ), repo, userConfig.authorName, userConfig.authorEmail,
2242 modifiedFiles );
2243 auto ret = dlg.ShowModal();
2244
2245 if( ret != wxID_OK )
2246 return;
2247
2248 std::vector<wxString> files = dlg.GetSelectedFiles();
2249
2250 if( dlg.GetCommitMessage().IsEmpty() )
2251 {
2252 wxMessageBox( _( "Discarding commit due to empty commit message." ) );
2253 return;
2254 }
2255
2256 if( files.empty() )
2257 {
2258 wxMessageBox( _( "Discarding commit due to empty file selection." ) );
2259 return;
2260 }
2261
2262 GIT_COMMIT_HANDLER commitHandler( repo );
2263 auto result = commitHandler.PerformCommit( files, dlg.GetCommitMessage(),
2264 dlg.GetAuthorName(), dlg.GetAuthorEmail() );
2265
2267 {
2268 wxMessageBox( wxString::Format( _( "Failed to create commit: %s" ),
2269 commitHandler.GetErrorString() ) );
2270 return;
2271 }
2272
2273 wxLogTrace( traceGit, wxS( "Created commit" ) );
2274 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
2275}
2276
2277
2278void PROJECT_TREE_PANE::onGitAddToIndex( wxCommandEvent& aEvent )
2279{
2280
2281}
2282
2283
2284bool PROJECT_TREE_PANE::canFileBeAddedToVCS( const wxString& aFile )
2285{
2286 if( !m_TreeProject->GetGitRepo() )
2287 return false;
2288
2289 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
2290 auto fileStatusMap = statusHandler.GetFileStatus();
2291
2292 // Check if file is already tracked or staged
2293 for( const auto& [filePath, fileStatus] : fileStatusMap )
2294 {
2295 if( filePath.EndsWith( aFile ) || filePath == aFile )
2296 {
2297 // File can be added if it's untracked
2298 return fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_UNTRACKED;
2299 }
2300 }
2301
2302 // If file not found in status, it might be addable
2303 return true;
2304}
2305
2306
2307void PROJECT_TREE_PANE::onGitSyncProject( wxCommandEvent& aEvent )
2308{
2309 wxLogTrace( traceGit, "Syncing project" );
2310 git_repository* repo = m_TreeProject->GetGitRepo();
2311
2312 if( !repo )
2313 {
2314 wxLogTrace( traceGit, "sync: No git repository found" );
2315 return;
2316 }
2317
2318 GIT_SYNC_HANDLER handler( repo );
2319 handler.PerformSync();
2320}
2321
2322
2323void PROJECT_TREE_PANE::onGitFetch( wxCommandEvent& aEvent )
2324{
2325 KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon();
2326
2327 if( !gitCommon )
2328 return;
2329
2330 GIT_PULL_HANDLER handler( gitCommon );
2331 handler.PerformFetch();
2332
2333 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
2334}
2335
2336
2337void PROJECT_TREE_PANE::onGitResolveConflict( wxCommandEvent& aEvent )
2338{
2339 git_repository* repo = m_TreeProject->GetGitRepo();
2340
2341 if( !repo )
2342 return;
2343
2344 GIT_RESOLVE_CONFLICT_HANDLER handler( repo );
2345 handler.PerformResolveConflict();
2346}
2347
2348
2349void PROJECT_TREE_PANE::onGitRevertLocal( wxCommandEvent& aEvent )
2350{
2351 git_repository* repo = m_TreeProject->GetGitRepo();
2352
2353 if( !repo )
2354 return;
2355
2356 GIT_REVERT_HANDLER handler( repo );
2357 handler.PerformRevert();
2358}
2359
2360
2361void PROJECT_TREE_PANE::onGitRemoveFromIndex( wxCommandEvent& aEvent )
2362{
2363 git_repository* repo = m_TreeProject->GetGitRepo();
2364
2365 if( !repo )
2366 return;
2367
2368 GIT_REMOVE_FROM_INDEX_HANDLER handler( repo );
2369 handler.PerformRemoveFromIndex();
2370}
2371
2372
2374{
2375
2376}
2377
2378
2379void PROJECT_TREE_PANE::onGitSyncTimer( wxTimerEvent& aEvent )
2380{
2381 wxLogTrace( traceGit, "onGitSyncTimer" );
2382 COMMON_SETTINGS::GIT& gitSettings = Pgm().GetCommonSettings()->m_Git;
2383
2384 if( !gitSettings.enableGit || !m_TreeProject )
2385 return;
2386
2388
2389 tp.detach_task( [this]()
2390 {
2391 KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon();
2392
2393 if( !gitCommon )
2394 {
2395 wxLogTrace( traceGit, "onGitSyncTimer: No git repository found" );
2396 return;
2397 }
2398
2399 // Check if cancellation was requested (e.g., during shutdown)
2400 if( gitCommon->IsCancelled() )
2401 {
2402 wxLogTrace( traceGit, "onGitSyncTimer: Cancelled" );
2403 return;
2404 }
2405
2406 GIT_PULL_HANDLER handler( gitCommon );
2407 handler.PerformFetch();
2408
2409 // Only schedule the follow-up work if not cancelled
2410 if( !gitCommon->IsCancelled() )
2411 CallAfter( [this]() { gitStatusTimerHandler(); } );
2412 } );
2413
2414 if( gitSettings.updatInterval > 0 )
2415 {
2416 wxLogTrace( traceGit, "onGitSyncTimer: Restarting git sync timer" );
2417 // We store the timer interval in minutes but wxTimer uses milliseconds
2418 m_gitSyncTimer.Start( gitSettings.updatInterval * 60 * 1000, wxTIMER_ONE_SHOT );
2419 }
2420}
2421
2422
2424{
2425 // Check if git is still available and not cancelled before spawning background work
2426 KIGIT_COMMON* gitCommon = m_TreeProject ? m_TreeProject->GitCommon() : nullptr;
2427
2428 if( !gitCommon || gitCommon->IsCancelled() )
2429 return;
2430
2433
2434 tp.detach_task( [this]() { updateGitStatusIconMap(); } );
2435}
2436
2437void PROJECT_TREE_PANE::onGitStatusTimer( wxTimerEvent& aEvent )
2438{
2439 wxLogTrace( traceGit, "onGitStatusTimer" );
2440
2441 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
2442 return;
2443
2445}
const char * name
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:104
BITMAP_STORE * GetBitmapStore()
Definition bitmap.cpp:92
@ directory_browser
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
void ThemeChanged()
Notifies the store that the icon theme has been changed by the user, so caches must be invalidated.
wxString GetCommitMessage() const
wxString GetAuthorEmail() const
std::vector< wxString > GetSelectedFiles() const
wxString GetAuthorName() const
KIGIT_COMMON::GIT_CONN_TYPE GetRepoType() const
wxString GetRepoSSHPath() const
void SetSkipButtonLabel(const wxString &aLabel)
wxString GetBranchName() const
int ShowModal() override
virtual bool IsLibraryAvailable()=0
BranchResult SwitchToBranch(const wxString &aBranchName)
Switch to the specified branch.
CommitResult PerformCommit(const std::vector< wxString > &aFiles, const wxString &aMessage, const wxString &aAuthorName, const wxString &aAuthorEmail)
wxString GetErrorString() const
GitUserConfig GetUserConfig()
Get user configuration (name and email) from git config Falls back to common settings if not found in...
bool IsRepository(const wxString &aPath)
Check if a directory is already a git repository.
InitResult InitializeRepository(const wxString &aPath)
Initialize a new git repository in the specified directory.
bool SetupRemote(const RemoteConfig &aConfig)
Set up a remote for the repository.
void SetProgressReporter(std::unique_ptr< WX_PROGRESS_REPORTER > aProgressReporter)
bool PerformFetch(bool aSkipLock=false)
PullResult PerformPull()
PushResult PerformPush()
wxString GetCurrentBranchName()
Get the current branch name.
void UpdateRemoteStatus(const std::set< wxString > &aLocalChanges, const std::set< wxString > &aRemoteChanges, std::map< wxString, FileStatus > &aFileStatus)
Get status for modified files based on local/remote changes.
wxString GetWorkingDirectory()
Get the repository working directory path.
bool HasChangedFiles()
Check if the repository has any changed files.
std::map< wxString, FileStatus > GetFileStatus(const wxString &aPathspec=wxEmptyString)
Get detailed file status for all files in the specified path.
The main KiCad project manager frame.
PROJECT_TREE_PANE * m_leftWin
void OnChangeWatchedPaths(wxCommandEvent &aEvent)
Called by sending a event with id = ID_INIT_WATCHED_PATHS rebuild the list of watched paths.
static git_repository * GetRepositoryForFile(const char *aFilename)
Discover and open the repository that contains the given file.
static bool RemoveVCS(git_repository *&aRepo, const wxString &aProjectPath=wxEmptyString, bool aRemoveGitDir=false, wxString *aErrors=nullptr)
Remove version control from a directory by freeing the repository and optionally removing the ....
static int CreateBranch(git_repository *aRepo, const wxString &aBranchName)
Create a new branch based on HEAD.
wxString GetGitRootDirectory() const
bool IsCancelled() const
bool HasPushAndPullRemote() const
bool HasLocalCommits() const
wxString GetErrorString()
git_repository * GetRepo() const
Get a pointer to the git repository.
void SetEllipsedTextField(const wxString &aText, int aFieldId)
Set the text in a field using wxELLIPSIZE_MIDDLE option to adjust the text size to the field size.
static wxString GetDefaultUserProjectsPath()
Gets the default path we point users to create projects.
Definition paths.cpp:137
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:547
virtual const wxString & GetTextEditor(bool aCanShowFileChooser=true)
Return the path to the preferred text editor application.
Definition pgm_base.cpp:214
The project local settings are things that are attached to a particular project, but also might be pa...
bool m_GitIntegrationDisabled
If true, KiCad will not use Git integration for this project even if a .git directory exists.
bool SaveToFile(const wxString &aDirectory="", bool aForce=false) override
Calls Store() and then writes the contents of the JSON document to a file.
Handle one item (a file or a directory name) for the tree file.
void SetRootFile(bool aValue)
void SetState(int state)
const wxString & GetFileName() const
bool IsPopulated() const
void SetPopulated(bool aValue)
TREE_FILE_TYPE GetType() const
const wxString GetDir() const
void Activate(PROJECT_TREE_PANE *aTreePrjFrame)
PROJECT_TREE_PANE Window to display the tree files.
PROJECT_TREE_ITEM * m_selectedItem
std::unordered_map< wxString, wxTreeItemId > m_gitTreeCache
void onGitFetch(wxCommandEvent &event)
Fetch the latest changes from the git repository.
std::map< wxTreeItemId, KIGIT_COMMON::GIT_STATUS > m_gitStatusIcons
void onGitInitializeProject(wxCommandEvent &event)
Initialize a new git repository in the current project directory.
void onGitSyncProject(wxCommandEvent &event)
Sync the current project with the git repository.
void onDeleteFile(wxCommandEvent &event)
Function onDeleteFile Delete the selected file or directory in the tree project.
void onGitRemoveVCS(wxCommandEvent &event)
Remove the git repository from the current project directory.
void EmptyTreePrj()
Delete all m_TreeProject entries.
void FileWatcherReset()
Reinit the watched paths Should be called after opening a new project to rebuild the list of watched ...
std::vector< PROJECT_TREE_ITEM * > GetSelectedData()
Function GetSelectedData return the item data from item currently selected (highlighted) Note this is...
void onOpenDirectory(wxCommandEvent &event)
Function onOpenDirectory Handles the right-click menu for opening a directory in the current system f...
void onGitResolveConflict(wxCommandEvent &event)
Resolve conflicts in the git repository.
void onFileSystemEvent(wxFileSystemWatcherEvent &event)
called when a file or directory is modified/created/deleted The tree project is modified when a file ...
wxFileSystemWatcher * m_watcher
void onRight(wxTreeEvent &Event)
Called on a right click on an item.
void onGitCommit(wxCommandEvent &event)
Commit the current project saved changes to the git repository.
void onSelect(wxTreeEvent &Event)
Called on a double click on an item.
PROJECT_TREE * m_TreeProject
PROJECT_TREE_PANE(KICAD_MANAGER_FRAME *parent)
KICAD_MANAGER_FRAME * m_Parent
void onExpand(wxTreeEvent &Event)
Called on a click on the + or - button of an item with children.
void onGitSyncTimer(wxTimerEvent &event)
void onIdle(wxIdleEvent &aEvent)
Idle event handler, used process the selected items at a point in time when all other events have bee...
void updateTreeCache()
Updates the map of the wxtreeitemid to the name of each file for use in the thread.
void onGitRevertLocal(wxCommandEvent &event)
Revert the local repository to the last commit.
void onGitPullProject(wxCommandEvent &event)
Pull the latest changes from the git repository.
static wxString GetFileExt(TREE_FILE_TYPE type)
friend class PROJECT_TREE_ITEM
void onGitRemoveFromIndex(wxCommandEvent &event)
Remove a file from the git index.
wxTreeItemId findSubdirTreeItem(const wxString &aSubDir)
Function findSubdirTreeItem searches for the item in tree project which is the node of the subdirecto...
void ReCreateTreePrj()
Create or modify the tree showing project file names.
void onThemeChanged(wxSysColourChangedEvent &aEvent)
void onRunSelectedJobsFile(wxCommandEvent &event)
Run a selected jobs file.
void onGitCompare(wxCommandEvent &event)
Compare the current project to a different branch in the git repository.
void onGitStatusTimer(wxTimerEvent &event)
void shutdownFileWatcher()
Shutdown the file watcher.
std::mutex m_gitTreeCacheMutex
wxTreeItemId addItemToProjectTree(const wxString &aName, const wxTreeItemId &aParent, std::vector< wxString > *aProjectNames, bool aRecurse)
Function addItemToProjectTree.
bool hasChangedFiles()
Returns true if the current project has any uncommitted changes.
void onOpenSelectedFileWithTextEditor(wxCommandEvent &event)
Function onOpenSelectedFileWithTextEditor Call the text editor to open the selected file in the tree ...
void onGitAddToIndex(wxCommandEvent &event)
Add a file to the git index.
void updateGitStatusIcons()
Updates the icons shown in the tree project to reflect the current git status.
void updateGitStatusIconMap()
This is a threaded call that will change the map of git status icons for use in the main thread.
PROJECT_TREE_ITEM * GetItemIdData(wxTreeItemId aId)
Function GetItemIdData return the item data corresponding to a wxTreeItemId identifier.
void onGitSwitchBranch(wxCommandEvent &event)
Switch to a different branch in the git repository.
void onCreateNewDirectory(wxCommandEvent &event)
Function onCreateNewDirectory Creates a new subdirectory inside the current kicad project directory t...
void onSwitchToSelectedProject(wxCommandEvent &event)
Switch to a other project selected from the tree project (by selecting an other .pro file inside the ...
void onPaint(wxPaintEvent &aEvent)
We don't have uniform borders so we have to draw them ourselves.
void onRenameFile(wxCommandEvent &event)
Function onRenameFile Rename the selected file or directory in the tree project.
void onGitPushProject(wxCommandEvent &event)
Push the current project changes to the git repository.
bool canFileBeAddedToVCS(const wxString &aFilePath)
Returns true if the file has already been added to the repository or false if it has not been added y...
std::vector< wxString > m_filters
PROJECT_TREE This is the class to show (as a tree) the files in the project directory.
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:167
virtual PROJECT_LOCAL_SETTINGS & GetLocalSettings() const
Definition project.h:205
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition confirm.cpp:230
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:202
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:177
This file is part of the common library.
#define _(s)
EVT_MENU_RANGE(ID_GERBVIEW_DRILL_FILE1, ID_GERBVIEW_DRILL_FILEMAX, GERBVIEW_FRAME::OnDrlFileHistory) EVT_MENU_RANGE(ID_GERBVIEW_ZIP_FILE1
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback, bool aFileForKicad)
Call the executable file aEditorName with the parameter aFileName.
Definition gestfich.cpp:145
GIT_BACKEND * GetGitBackend()
InitResult
int m_GitIconRefreshInterval
The interval in milliseconds to refresh the git icons in the project tree.
static const std::string LegacySchematicFileExtension
static const std::string HtmlFileExtension
static const wxString GerberFileExtensionsRegex
static const std::string NetlistFileExtension
static const std::string GerberJobFileExtension
static const std::string ReportFileExtension
static const std::string LockFileExtension
static const std::string ProjectFileExtension
static const std::string FootprintPlaceFileExtension
static const std::string LegacyPcbFileExtension
static const std::string LegacyProjectFileExtension
static const std::string KiCadSchematicFileExtension
static const std::string LegacySymbolLibFileExtension
static const std::string LockFilePrefix
static const std::string KiCadSymbolLibFileExtension
static const std::string SpiceFileExtension
static const std::string PdfFileExtension
static const std::string TextFileExtension
static const std::string DrawingSheetFileExtension
static const std::string BackupFileSuffix
static const std::string KiCadJobSetFileExtension
static const std::string FootprintAssignmentFileExtension
static const std::string SVGFileExtension
static const std::string DrillFileExtension
static const std::string DesignRulesFileExtension
static const std::string MarkdownFileExtension
static const std::string KiCadFootprintFileExtension
static const std::string ArchiveFileExtension
static const std::string KiCadPcbFileExtension
const wxChar *const tracePathsAndFiles
Flag to enable path and file name debug output.
const wxChar *const traceGit
Flag to enable Git debugging output.
PROJECT & Prj()
Definition kicad.cpp:637
@ ID_PROJECT_TREE
Definition kicad_id.h:36
@ ID_LEFT_FRAME
Definition kicad_id.h:35
bool LaunchExternal(const wxString &aPath)
Launches the given file or folder in the host OS.
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:83
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 IsFileHidden(const wxString &aFileName)
Helper function to determine the status of the 'Hidden' file attribute.
Definition unix/io.cpp:85
bool StoreSecret(const wxString &aService, const wxString &aKey, const wxString &aSecret)
KICOMMON_API wxMenuItem * AddMenuItem(wxMenu *aMenu, int aId, const wxString &aText, const wxBitmapBundle &aImage, wxItemKind aType=wxITEM_NORMAL)
Create and insert a menu item with an icon into aMenu.
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:100
wxDECLARE_EVENT(wxCUSTOM_PANEL_SHOWN_EVENT, wxCommandEvent)
PGM_BASE & Pgm()
The global program "get" accessor.
#define NAMELESS_PROJECT
default name for nameless projects
Definition project.h:44
project_tree_ids
The frame that shows the tree list of files and subdirectories inside the working directory.
@ ID_PROJECT_OPEN_DIR
@ ID_GIT_PUSH
@ ID_GIT_REVERT_LOCAL
@ ID_JOBS_RUN
@ ID_PROJECT_SWITCH_TO_OTHER
@ ID_PROJECT_NEWDIR
@ ID_PROJECT_RENAME
@ ID_GIT_SWITCH_QUICK5
@ ID_GIT_REMOVE_VCS
@ ID_GIT_COMMIT_FILE
@ ID_GIT_PULL
@ ID_GIT_COMPARE
@ ID_PROJECT_TXTEDIT
@ ID_GIT_SWITCH_QUICK2
@ ID_GIT_REMOVE_FROM_INDEX
@ ID_GIT_RESOLVE_CONFLICT
@ ID_GIT_CLONE_PROJECT
@ ID_GIT_SWITCH_QUICK4
@ ID_GIT_SWITCH_QUICK3
@ ID_GIT_COMMIT_PROJECT
@ ID_GIT_SWITCH_BRANCH
@ ID_GIT_ADD_TO_INDEX
@ ID_GIT_SYNC_PROJECT
@ ID_GIT_FETCH
@ ID_GIT_SWITCH_QUICK1
@ ID_GIT_INITIALIZE_PROJECT
@ ID_PROJECT_DELETE
std::vector< wxString > getProjects(const wxDir &dir)
static const wxChar * s_allowedExtensionsToList[]
#define wxFileSystemWatcher
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
KIGIT_COMMON::GIT_CONN_TYPE connType
std::string path
wxString result
Test unit parsing edge cases and error handling.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
static thread_pool * tp
BS::priority_thread_pool thread_pool
Definition thread_pool.h:31
wxLogTrace helper definitions.
TREE_FILE_TYPE
Definition of file extensions used in Kicad.
#define PR_NO_ABORT