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 wxDir parentDir( parentTreeItem->GetDir() );
502 std::vector<wxString> projects = getProjects( parentDir );
503
504 if( !alg::contains( projects, currfile.GetName() ) )
505 return wxTreeItemId();
506 }
507 }
508
509 // also check to see if it is already there.
510 wxTreeItemIdValue cookie;
511 wxTreeItemId kid = m_TreeProject->GetFirstChild( aParent, cookie );
512
513 while( kid.IsOk() )
514 {
515 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
516
517 if( itemData && itemData->GetFileName() == aName )
518 return itemData->GetId(); // well, we would have added it, but it is already here!
519
520 kid = m_TreeProject->GetNextChild( aParent, cookie );
521 }
522
523 // Only show current files if both legacy and current files are present
528 {
529 kid = m_TreeProject->GetFirstChild( aParent, cookie );
530
531 while( kid.IsOk() )
532 {
533 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
534
535 if( itemData )
536 {
537 wxFileName fname( itemData->GetFileName() );
538
539 if( fname.GetName().CmpNoCase( currfile.GetName() ) == 0 )
540 {
541 switch( type )
542 {
544 if( itemData->GetType() == TREE_FILE_TYPE::JSON_PROJECT )
545 return wxTreeItemId();
546
547 break;
548
550 if( itemData->GetType() == TREE_FILE_TYPE::SEXPR_SCHEMATIC )
551 return wxTreeItemId();
552
553 break;
554
556 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_PROJECT )
557 m_TreeProject->Delete( kid );
558
559 break;
560
562 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_SCHEMATIC )
563 m_TreeProject->Delete( kid );
564
565 break;
566
567 default:
568 break;
569 }
570 }
571 }
572
573 kid = m_TreeProject->GetNextChild( aParent, cookie );
574 }
575 }
576
577 // Append the item (only appending the filename not the full path):
578 wxTreeItemId newItemId = m_TreeProject->AppendItem( aParent, file );
579 PROJECT_TREE_ITEM* data = new PROJECT_TREE_ITEM( type, aName, m_TreeProject );
580
581 m_TreeProject->SetItemData( newItemId, data );
582 data->SetState( 0 );
583
584 // Mark root files (files which have the same aName as the project)
585 wxString fileName = currfile.GetName().Lower();
586 wxString projName = project.GetName().Lower();
587
588 if( fileName == projName || fileName.StartsWith( projName + "-" ) )
589 data->SetRootFile( true );
590
591#ifndef __WINDOWS__
592 bool subdir_populated = false;
593#endif
594
595 // This section adds dirs and files found in the subdirs
596 // in this case AddFile is recursive, but for the first level only.
597 if( TREE_FILE_TYPE::DIRECTORY == type && aRecurse )
598 {
599 wxDir dir( aName );
600
601 if( dir.IsOpened() ) // protected dirs will not open properly.
602 {
603 std::vector<wxString> projects = getProjects( dir );
604 wxString dir_filename;
605 bool haveFile = dir.GetFirst( &dir_filename );
606
607 data->SetPopulated( true );
608
609#ifndef __WINDOWS__
610 subdir_populated = aRecurse;
611#endif
612
613 while( haveFile )
614 {
615 // Add name in tree, but do not recurse
616 wxString path = aName + wxFileName::GetPathSeparator() + dir_filename;
617 addItemToProjectTree( path, newItemId, &projects, false );
618
619 haveFile = dir.GetNext( &dir_filename );
620 }
621 }
622
623 // Sort filenames by alphabetic order
624 m_TreeProject->SortChildren( newItemId );
625 }
626
627#ifndef __WINDOWS__
628 if( subdir_populated )
629 m_watcherNeedReset = true;
630#endif
631
632 return newItemId;
633}
634
635
637{
638 std::lock_guard<std::mutex> lock1( m_gitStatusMutex );
639 std::lock_guard<std::mutex> lock2( m_gitTreeCacheMutex );
641
642 while( tp.get_tasks_running() )
643 {
644 tp.wait_for( std::chrono::milliseconds( 250 ) );
645 }
646
647 m_gitStatusTimer.Stop();
648 m_gitSyncTimer.Stop();
649 m_gitTreeCache.clear();
650 m_gitStatusIcons.clear();
651
652 wxString pro_dir = m_Parent->GetProjectFileName();
653
654 if( !m_TreeProject )
655 m_TreeProject = new PROJECT_TREE( this );
656 else
657 m_TreeProject->DeleteAllItems();
658
659 if( !pro_dir ) // This is empty from PROJECT_TREE_PANE constructor
660 return;
661
662 if( m_TreeProject->GetGitRepo() )
663 {
664 git_repository* repo = m_TreeProject->GetGitRepo();
666 m_TreeProject->SetGitRepo( nullptr );
667 m_gitIconsInitialized = false;
668 }
669
670 wxFileName fn = pro_dir;
671 bool prjReset = false;
672
673 if( !fn.IsOk() )
674 {
675 fn.Clear();
676 fn.SetPath( PATHS::GetDefaultUserProjectsPath() );
677 fn.SetName( NAMELESS_PROJECT );
679 prjReset = true;
680 }
681
682 bool prjOpened = fn.FileExists();
683
684 // Bind the git repository to the project tree (if it exists and not disabled for this project)
685 if( Pgm().GetCommonSettings()->m_Git.enableGit
686 && !Prj().GetLocalSettings().m_GitIntegrationDisabled )
687 {
688 m_TreeProject->SetGitRepo( KIGIT::PROJECT_GIT_UTILS::GetRepositoryForFile( fn.GetPath().c_str() ) );
689
690 if( m_TreeProject->GetGitRepo() )
691 {
692 m_TreeProject->GitCommon()->SetUsername( Prj().GetLocalSettings().m_GitRepoUsername );
693 m_TreeProject->GitCommon()->SetSSHKey( Prj().GetLocalSettings().m_GitSSHKey );
694 m_TreeProject->GitCommon()->UpdateCurrentBranchInfo();
695 }
696 }
697
698 // We may have opened a legacy project, in which case GetProjectFileName will return the
699 // name of the migrated (new format) file, which may not have been saved to disk yet.
700 if( !prjOpened && !prjReset )
701 {
703 prjOpened = fn.FileExists();
704
705 // Set the ext back so that in the tree view we see the (not-yet-saved) new file
707 }
708
709 // root tree:
710 m_root = m_TreeProject->AddRoot( fn.GetFullName(), static_cast<int>( TREE_FILE_TYPE::ROOT ),
711 static_cast<int>( TREE_FILE_TYPE::ROOT ) );
712 m_TreeProject->SetItemBold( m_root, true );
713
714 // The main project file is now a JSON file
717
718 m_TreeProject->SetItemData( m_root, data );
719
720 // Now adding all current files if available
721 if( prjOpened )
722 {
723 pro_dir = wxPathOnly( m_Parent->GetProjectFileName() );
724 wxDir dir( pro_dir );
725
726 if( dir.IsOpened() ) // protected dirs will not open, see "man opendir()"
727 {
728 std::vector<wxString> projects = getProjects( dir );
729 wxString filename;
730 bool haveFile = dir.GetFirst( &filename );
731
732 while( haveFile )
733 {
734 if( filename != fn.GetFullName() )
735 {
736 wxString name = dir.GetName() + wxFileName::GetPathSeparator() + filename;
737
738 // Add items living in the project directory, and populate the item
739 // if it is a directory (sub directories will be not populated)
740 addItemToProjectTree( name, m_root, &projects, true );
741 }
742
743 haveFile = dir.GetNext( &filename );
744 }
745 }
746 }
747 else
748 {
749 m_TreeProject->AppendItem( m_root, wxT( "Empty project" ) );
750 }
751
752 m_TreeProject->Expand( m_root );
753
754 // Sort filenames by alphabetic order
755 m_TreeProject->SortChildren( m_root );
756
757 CallAfter(
758 [this] ()
759 {
760 wxLogTrace( traceGit, "PROJECT_TREE_PANE::ReCreateTreePrj: starting timers" );
761 m_gitSyncTimer.Start( 100, wxTIMER_ONE_SHOT );
762 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
763 } );
764}
765
766
768{
769 if( !m_TreeProject->GetGitRepo() )
770 return false;
771
772 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
773 return statusHandler.HasChangedFiles();
774}
775
776
777void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event )
778{
779 wxTreeItemId curr_item = Event.GetItem();
780
781 // Ensure item is selected (Under Windows right click does not select the item)
782 m_TreeProject->SelectItem( curr_item );
783
784 std::vector<PROJECT_TREE_ITEM*> selection = GetSelectedData();
785 KIGIT_COMMON* git = m_TreeProject->GitCommon();
786 wxFileName prj_dir( Prj().GetProjectPath(), wxEmptyString );
787 wxFileName git_dir( git->GetGitRootDirectory(), wxEmptyString );
788 prj_dir.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_CASE | wxPATH_NORM_DOTS
789 | wxPATH_NORM_ENV_VARS | wxPATH_NORM_TILDE );
790 git_dir.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_CASE | wxPATH_NORM_DOTS
791 | wxPATH_NORM_ENV_VARS | wxPATH_NORM_TILDE );
792 wxString prj_name = prj_dir.GetFullPath();
793 wxString git_name = git_dir.GetFullPath();
794
795 bool can_switch_to_project = true;
796 bool can_create_new_directory = true;
797 bool can_open_this_directory = true;
798 bool can_edit = true;
799 bool can_rename = true;
800 bool can_delete = true;
801 bool run_jobs = false;
802
803 bool vcs_has_repo = m_TreeProject->GetGitRepo() != nullptr;
804 bool vcs_can_commit = hasChangedFiles();
805 bool vcs_can_init = !vcs_has_repo;
806 bool gitIntegrationDisabled = Prj().GetLocalSettings().m_GitIntegrationDisabled;
807 // Allow toggling if: repo exists in project, OR integration is disabled (to re-enable it)
808 bool vcs_can_remove = ( vcs_has_repo && git_name.StartsWith( prj_name ) ) || gitIntegrationDisabled;
809 bool vcs_can_fetch = vcs_has_repo && git->HasPushAndPullRemote();
810 bool vcs_can_push = vcs_can_fetch && git->HasLocalCommits();
811 bool vcs_can_pull = vcs_can_fetch;
812 bool vcs_can_switch = vcs_has_repo;
813 bool vcs_menu = Pgm().GetCommonSettings()->m_Git.enableGit;
814
815 // Check if the libgit2 library is available via backend
816 bool libgit_init = GetGitBackend() && GetGitBackend()->IsLibraryAvailable();
817
818 vcs_menu &= libgit_init;
819
820 if( selection.size() == 0 )
821 return;
822
823 // Remove things that don't make sense for multiple selections
824 if( selection.size() != 1 )
825 {
826 can_switch_to_project = false;
827 can_create_new_directory = false;
828 can_rename = false;
829 }
830
831 for( PROJECT_TREE_ITEM* item : selection )
832 {
833 // Check for empty project
834 if( !item )
835 {
836 can_switch_to_project = false;
837 can_edit = false;
838 can_rename = false;
839 continue;
840 }
841
842 can_delete = item->CanDelete();
843 can_rename = item->CanRename();
844
845 switch( item->GetType() )
846 {
849 can_rename = false;
850
851 if( item->GetId() == m_TreeProject->GetRootItem() )
852 {
853 can_switch_to_project = false;
854 }
855 else
856 {
857 can_create_new_directory = false;
858 can_open_this_directory = false;
859 }
860 break;
861
863 can_switch_to_project = false;
864 can_edit = false;
865 break;
866
869 can_edit = false;
870 can_switch_to_project = false;
871 can_create_new_directory = false;
872 can_open_this_directory = false;
873 break;
874
876 run_jobs = true;
877 can_edit = false;
879
883
884 default:
885 can_switch_to_project = false;
886 can_create_new_directory = false;
887 can_open_this_directory = false;
888
889 break;
890 }
891 }
892
893 wxMenu popup_menu;
894 wxString text;
895 wxString help_text;
896
897 if( can_switch_to_project )
898 {
899 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_SWITCH_TO_OTHER, _( "Switch to this Project" ),
900 _( "Close all editors, and switch to the selected project" ),
902 popup_menu.AppendSeparator();
903 }
904
905 if( can_create_new_directory )
906 {
907 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_NEWDIR, _( "New Directory..." ),
908 _( "Create a New Directory" ), KiBitmap( BITMAPS::directory ) );
909 }
910
911 if( can_open_this_directory )
912 {
913 if( selection.size() == 1 )
914 {
915#ifdef __APPLE__
916 text = _( "Reveal in Finder" );
917 help_text = _( "Reveals the directory in a Finder window" );
918#else
919 text = _( "Open Directory in File Explorer" );
920 help_text = _( "Opens the directory in the default system file manager" );
921#endif
922 }
923 else
924 {
925#ifdef __APPLE__
926 text = _( "Reveal in Finder" );
927 help_text = _( "Reveals the directories in a Finder window" );
928#else
929 text = _( "Open Directories in File Explorer" );
930 help_text = _( "Opens the directories in the default system file manager" );
931#endif
932 }
933
934 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_OPEN_DIR, text, help_text,
936 }
937
938 if( can_edit )
939 {
940 if( selection.size() == 1 )
941 help_text = _( "Open the file in a Text Editor" );
942 else
943 help_text = _( "Open files in a Text Editor" );
944
945 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_TXTEDIT, _( "Edit in a Text Editor" ), help_text,
947 }
948
949 if( run_jobs && selection.size() == 1 )
950 {
951 KIUI::AddMenuItem( &popup_menu, ID_JOBS_RUN, _( "Run Jobs" ), help_text,
953 }
954
955 if( can_rename )
956 {
957 if( selection.size() == 1 )
958 {
959 text = _( "Rename File..." );
960 help_text = _( "Rename file" );
961 }
962 else
963 {
964 text = _( "Rename Files..." );
965 help_text = _( "Rename files" );
966 }
967
968 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_RENAME, text, help_text,
970 }
971
972 if( can_delete )
973 {
974 if( selection.size() == 1 )
975 help_text = _( "Delete the file and its content" );
976 else
977 help_text = _( "Delete the files and their contents" );
978
979 if( can_switch_to_project
980 || can_create_new_directory
981 || can_open_this_directory
982 || can_edit
983 || can_rename )
984 {
985 popup_menu.AppendSeparator();
986 }
987
988#ifdef __WINDOWS__
989 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Delete" ), help_text,
991#else
992 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Move to Trash" ), help_text,
994#endif
995 }
996
997 if( vcs_menu )
998 {
999 wxMenu* vcs_submenu = new wxMenu();
1000 wxMenu* branch_submenu = new wxMenu();
1001 wxMenuItem* vcs_menuitem = nullptr;
1002
1003 vcs_menuitem = vcs_submenu->Append( ID_GIT_INITIALIZE_PROJECT,
1004 _( "Add Project to Version Control..." ),
1005 _( "Initialize a new repository" ) );
1006 vcs_menuitem->Enable( vcs_can_init );
1007
1008
1009 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_PROJECT, _( "Commit Project..." ),
1010 _( "Commit changes to the local repository" ) );
1011 vcs_menuitem->Enable( vcs_can_commit );
1012
1013 vcs_menuitem = vcs_submenu->Append( ID_GIT_PUSH, _( "Push" ),
1014 _( "Push committed local changes to remote repository" ) );
1015 vcs_menuitem->Enable( vcs_can_push );
1016
1017 vcs_menuitem = vcs_submenu->Append( ID_GIT_PULL, _( "Pull" ),
1018 _( "Pull changes from remote repository into local" ) );
1019 vcs_menuitem->Enable( vcs_can_pull );
1020
1021 vcs_submenu->AppendSeparator();
1022
1023 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_FILE, _( "Commit File..." ),
1024 _( "Commit changes to the local repository" ) );
1025 vcs_menuitem->Enable( vcs_can_commit );
1026
1027 vcs_submenu->AppendSeparator();
1028
1029 // vcs_menuitem = vcs_submenu->Append( ID_GIT_COMPARE, _( "Diff" ),
1030 // _( "Show changes between the repository and working tree" ) );
1031 // vcs_menuitem->Enable( vcs_can_diff );
1032
1033 std::vector<wxString> branchNames = m_TreeProject->GitCommon()->GetBranchNames();
1034
1035 // Skip the first one (that is the current branch)
1036 for( size_t ii = 1; ii < branchNames.size() && ii < 6; ++ii )
1037 {
1038 wxString msg = _( "Switch to branch " ) + branchNames[ii];
1039 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH + ii, branchNames[ii], msg );
1040 vcs_menuitem->Enable( vcs_can_switch );
1041 }
1042
1043 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Other..." ),
1044 _( "Switch to a different branch" ) );
1045 vcs_menuitem->Enable( vcs_can_switch );
1046
1047 vcs_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Switch to Branch" ), branch_submenu );
1048
1049 vcs_submenu->AppendSeparator();
1050
1051 if( gitIntegrationDisabled )
1052 {
1053 vcs_menuitem = vcs_submenu->Append( ID_GIT_REMOVE_VCS, _( "Enable Git Integration" ),
1054 _( "Re-enable Git integration for this project" ) );
1055 }
1056 else
1057 {
1058 vcs_menuitem = vcs_submenu->Append( ID_GIT_REMOVE_VCS, _( "Disable Git Integration" ),
1059 _( "Disable Git integration for this project" ) );
1060 }
1061
1062 vcs_menuitem->Enable( vcs_can_remove );
1063
1064 popup_menu.AppendSeparator();
1065 popup_menu.AppendSubMenu( vcs_submenu, _( "Version Control" ) );
1066 }
1067
1068 if( popup_menu.GetMenuItemCount() > 0 )
1069 PopupMenu( &popup_menu );
1070}
1071
1072
1074{
1075 wxString editorname = Pgm().GetTextEditor();
1076
1077 if( editorname.IsEmpty() )
1078 {
1079 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
1080 return;
1081 }
1082
1083 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1084
1085 for( PROJECT_TREE_ITEM* item_data : tree_data )
1086 {
1087 wxString fullFileName = item_data->GetFileName();
1088
1089 if( !fullFileName.IsEmpty() )
1090 {
1091 ExecuteFile( editorname, fullFileName.wc_str(), nullptr, false );
1092 }
1093 }
1094}
1095
1096
1097void PROJECT_TREE_PANE::onDeleteFile( wxCommandEvent& event )
1098{
1099 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1100
1101 for( PROJECT_TREE_ITEM* item_data : tree_data )
1102 item_data->Delete();
1103}
1104
1105
1106void PROJECT_TREE_PANE::onRenameFile( wxCommandEvent& event )
1107{
1108 wxTreeItemId curr_item = m_TreeProject->GetFocusedItem();
1109 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1110
1111 // XXX: Unnecessary?
1112 if( tree_data.size() != 1 )
1113 return;
1114
1115 wxString buffer = m_TreeProject->GetItemText( curr_item );
1116 wxString msg = wxString::Format( _( "Change filename: '%s'" ),
1117 tree_data[0]->GetFileName() );
1118 wxTextEntryDialog dlg( wxGetTopLevelParent( this ), msg, _( "Change filename" ), buffer );
1119
1120 if( dlg.ShowModal() != wxID_OK )
1121 return; // canceled by user
1122
1123 buffer = dlg.GetValue();
1124 buffer.Trim( true );
1125 buffer.Trim( false );
1126
1127 if( buffer.IsEmpty() )
1128 return; // empty file name not allowed
1129
1130 tree_data[0]->Rename( buffer, true );
1131 m_isRenaming = true;
1132}
1133
1134
1135void PROJECT_TREE_PANE::onSelect( wxTreeEvent& Event )
1136{
1137 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1138
1139 if( tree_data.size() != 1 )
1140 return;
1141
1142 // Bookmark the selected item but don't try and activate it until later. If we do it now,
1143 // there will be more events at least on Windows in this frame that will steal focus from
1144 // any newly launched windows
1145 m_selectedItem = tree_data[0];
1146}
1147
1148
1149void PROJECT_TREE_PANE::onIdle( wxIdleEvent& aEvent )
1150{
1151 // Idle executes once all other events finished processing. This makes it ideal to launch
1152 // a new window without starting Focus wars.
1153 if( m_watcherNeedReset )
1154 {
1155 m_selectedItem = nullptr;
1157 }
1158
1159 if( m_selectedItem != nullptr && m_TreeProject->GetRootItem().IsOk() )
1160 {
1161 // Make sure m_selectedItem still exists in the tree before activating it.
1162 std::vector<wxTreeItemId> validItemIds;
1163 m_TreeProject->GetItemsRecursively( m_TreeProject->GetRootItem(), validItemIds );
1164
1165 for( wxTreeItemId id : validItemIds )
1166 {
1167 if( GetItemIdData( id ) == m_selectedItem )
1168 {
1169 // Activate launches a window which may run the event loop on top of us and cause
1170 // onIdle to get called again, so be sure to null out m_selectedItem first.
1172 m_selectedItem = nullptr;
1173
1174 item->Activate( this );
1175 break;
1176 }
1177 }
1178 }
1179}
1180
1181
1182void PROJECT_TREE_PANE::onExpand( wxTreeEvent& Event )
1183{
1184 wxTreeItemId itemId = Event.GetItem();
1185 PROJECT_TREE_ITEM* tree_data = GetItemIdData( itemId );
1186
1187 if( !tree_data )
1188 return;
1189
1190 if( tree_data->GetType() != TREE_FILE_TYPE::DIRECTORY )
1191 return;
1192
1193 // explore list of non populated subdirs, and populate them
1194 wxTreeItemIdValue cookie;
1195 wxTreeItemId kid = m_TreeProject->GetFirstChild( itemId, cookie );
1196
1197#ifndef __WINDOWS__
1198 bool subdir_populated = false;
1199#endif
1200
1201 for( ; kid.IsOk(); kid = m_TreeProject->GetNextChild( itemId, cookie ) )
1202 {
1203 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1204
1205 if( !itemData || itemData->GetType() != TREE_FILE_TYPE::DIRECTORY )
1206 continue;
1207
1208 if( itemData->IsPopulated() )
1209 continue;
1210
1211 wxString fileName = itemData->GetFileName();
1212 wxDir dir( fileName );
1213
1214 if( dir.IsOpened() )
1215 {
1216 std::vector<wxString> projects = getProjects( dir );
1217 wxString dir_filename;
1218 bool haveFile = dir.GetFirst( &dir_filename );
1219
1220 while( haveFile )
1221 {
1222 // Add name to tree item, but do not recurse in subdirs:
1223 wxString name = fileName + wxFileName::GetPathSeparator() + dir_filename;
1224 addItemToProjectTree( name, kid, &projects, false );
1225
1226 haveFile = dir.GetNext( &dir_filename );
1227 }
1228
1229 itemData->SetPopulated( true ); // set state to populated
1230
1231#ifndef __WINDOWS__
1232 subdir_populated = true;
1233#endif
1234 }
1235
1236 // Sort filenames by alphabetic order
1237 m_TreeProject->SortChildren( kid );
1238 }
1239
1240#ifndef __WINDOWS__
1241 if( subdir_populated )
1242 m_watcherNeedReset = true;
1243#endif
1244}
1245
1246
1247std::vector<PROJECT_TREE_ITEM*> PROJECT_TREE_PANE::GetSelectedData()
1248{
1249 wxArrayTreeItemIds selection;
1250 std::vector<PROJECT_TREE_ITEM*> data;
1251
1252 m_TreeProject->GetSelections( selection );
1253
1254 for( const wxTreeItemId itemId : selection )
1255 {
1256 PROJECT_TREE_ITEM* item = GetItemIdData( itemId );
1257
1258 if( !item )
1259 {
1260 wxLogTrace( traceGit, wxS( "Null tree item returned for selection, dynamic_cast failed?" ) );
1261 continue;
1262 }
1263
1264 data.push_back( item );
1265 }
1266
1267 return data;
1268}
1269
1270
1272{
1273 return dynamic_cast<PROJECT_TREE_ITEM*>( m_TreeProject->GetItemData( aId ) );
1274}
1275
1276
1277wxTreeItemId PROJECT_TREE_PANE::findSubdirTreeItem( const wxString& aSubDir )
1278{
1279 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1280
1281 // If the subdir is the current working directory, return m_root
1282 // in main list:
1283 if( prj_dir == aSubDir )
1284 return m_root;
1285
1286 // The subdir is in the main tree or in a subdir: Locate it
1287 wxTreeItemIdValue cookie;
1288 wxTreeItemId root_id = m_root;
1289 std::stack<wxTreeItemId> subdirs_id;
1290
1291 wxTreeItemId child = m_TreeProject->GetFirstChild( root_id, cookie );
1292
1293 while( true )
1294 {
1295 if( ! child.IsOk() )
1296 {
1297 if( subdirs_id.empty() ) // all items were explored
1298 {
1299 root_id = child; // Not found: return an invalid wxTreeItemId
1300 break;
1301 }
1302 else
1303 {
1304 root_id = subdirs_id.top();
1305 subdirs_id.pop();
1306 child = m_TreeProject->GetFirstChild( root_id, cookie );
1307
1308 if( !child.IsOk() )
1309 continue;
1310 }
1311 }
1312
1313 PROJECT_TREE_ITEM* itemData = GetItemIdData( child );
1314
1315 if( itemData && ( itemData->GetType() == TREE_FILE_TYPE::DIRECTORY ) )
1316 {
1317 if( itemData->GetFileName() == aSubDir ) // Found!
1318 {
1319 root_id = child;
1320 break;
1321 }
1322
1323 // child is a subdir, push in list to explore it later
1324 if( itemData->IsPopulated() )
1325 subdirs_id.push( child );
1326 }
1327
1328 child = m_TreeProject->GetNextChild( root_id, cookie );
1329 }
1330
1331 return root_id;
1332}
1333
1334
1335void PROJECT_TREE_PANE::onFileSystemEvent( wxFileSystemWatcherEvent& event )
1336{
1337 // No need to process events when we're shutting down
1338 if( !m_watcher )
1339 return;
1340
1341 // Ignore events that are not file creation, deletion, renaming or modification because
1342 // they are not relevant to the project tree.
1343 if( !( event.GetChangeType() & ( wxFSW_EVENT_CREATE |
1344 wxFSW_EVENT_DELETE |
1345 wxFSW_EVENT_RENAME |
1346 wxFSW_EVENT_MODIFY ) ) )
1347 {
1348 return;
1349 }
1350
1351 const wxFileName& pathModified = event.GetPath();
1352 wxString subdir = pathModified.GetPath();
1353 wxString fn = pathModified.GetFullPath();
1354
1355 // Adjust directories to look like a file item (path and name).
1356 if( pathModified.GetFullName().IsEmpty() )
1357 {
1358 subdir = subdir.BeforeLast( '/' );
1359 fn = fn.BeforeLast( '/' );
1360 }
1361
1362 wxTreeItemId root_id = findSubdirTreeItem( subdir );
1363
1364 if( !root_id.IsOk() )
1365 return;
1366
1367 CallAfter( [this] ()
1368 {
1369 wxLogTrace( traceGit, wxS( "File system event detected, updating tree cache" ) );
1370 m_gitStatusTimer.Start( 1500, wxTIMER_ONE_SHOT );
1371 } );
1372
1373 wxTreeItemIdValue cookie; // dummy variable needed by GetFirstChild()
1374 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1375
1376 switch( event.GetChangeType() )
1377 {
1378 case wxFSW_EVENT_CREATE:
1379 {
1380 wxTreeItemId newitem = addItemToProjectTree( fn, root_id, nullptr, true );
1381
1382 // If we are in the process of renaming a file, select the new one
1383 // This is needed for MSW and OSX, since we don't get RENAME events from them, just a
1384 // pair of DELETE and CREATE events.
1385 if( m_isRenaming && newitem.IsOk() )
1386 {
1387 m_TreeProject->SelectItem( newitem );
1388 m_isRenaming = false;
1389 }
1390 }
1391 break;
1392
1393 case wxFSW_EVENT_DELETE:
1394 while( kid.IsOk() )
1395 {
1396 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1397
1398 if( itemData && itemData->GetFileName() == fn )
1399 {
1400 m_TreeProject->Delete( kid );
1401 return;
1402 }
1403 kid = m_TreeProject->GetNextChild( root_id, cookie );
1404 }
1405 break;
1406
1407 case wxFSW_EVENT_RENAME :
1408 {
1409 const wxFileName& newpath = event.GetNewPath();
1410 wxString newdir = newpath.GetPath();
1411 wxString newfn = newpath.GetFullPath();
1412
1413 while( kid.IsOk() )
1414 {
1415 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1416
1417 if( itemData && itemData->GetFileName() == fn )
1418 {
1419 m_TreeProject->Delete( kid );
1420 break;
1421 }
1422
1423 kid = m_TreeProject->GetNextChild( root_id, cookie );
1424 }
1425
1426 // Add the new item only if it is not the current project file (root item).
1427 // Remember: this code is called by a wxFileSystemWatcherEvent event, and not always
1428 // called after an actual file rename, and the cleanup code does not explore the
1429 // root item, because it cannot be renamed by the user. Also, ensure the new file
1430 // actually exists on the file system before it is readded. On Linux, moving a file
1431 // to the trash can cause the same path to be returned in both the old and new paths
1432 // of the event, even though the file isn't there anymore.
1433 PROJECT_TREE_ITEM* rootData = GetItemIdData( root_id );
1434
1435 if( rootData && newpath.Exists() && ( newfn != rootData->GetFileName() ) )
1436 {
1437 wxTreeItemId newroot_id = findSubdirTreeItem( newdir );
1438 wxTreeItemId newitem = addItemToProjectTree( newfn, newroot_id, nullptr, true );
1439
1440 // If the item exists, select it
1441 if( newitem.IsOk() )
1442 m_TreeProject->SelectItem( newitem );
1443 }
1444
1445 m_isRenaming = false;
1446 }
1447 break;
1448
1449 default:
1450 return;
1451 }
1452
1453 // Sort filenames by alphabetic order
1454 m_TreeProject->SortChildren( root_id );
1455}
1456
1457
1459{
1460 m_watcherNeedReset = false;
1461
1462 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1463
1464#if defined( _WIN32 )
1465 KISTATUSBAR* statusBar = static_cast<KISTATUSBAR*>( m_Parent->GetStatusBar() );
1466
1467 if( KIPLATFORM::ENV::IsNetworkPath( prj_dir ) )
1468 {
1469 // Due to a combination of a bug in SAMBA sending bad change event IDs and wxWidgets
1470 // choosing to fault on an invalid event ID instead of sanely ignoring them we need to
1471 // avoid spawning a filewatcher. Unfortunately this punishes corporate environments with
1472 // Windows Server shares :/
1473 m_Parent->m_FileWatcherInfo = _( "Network path: not monitoring folder changes" );
1474 statusBar->SetEllipsedTextField( m_Parent->m_FileWatcherInfo, 1 );
1475 return;
1476 }
1477 else
1478 {
1479 m_Parent->m_FileWatcherInfo = _( "Local path: monitoring folder changes" );
1480 statusBar->SetEllipsedTextField( m_Parent->m_FileWatcherInfo, 1 );
1481 }
1482#endif
1483
1484 // Prepare file watcher:
1485 if( m_watcher )
1486 {
1487 m_watcher->RemoveAll();
1488 }
1489 else
1490 {
1491 // Create a wxWidgets log handler to catch errors during watcher creation
1492 // We need to to this because we cannot get error codes from the wxFileSystemWatcher
1493 // constructor. On Linux, if inotify cannot be initialized (usually due to resource limits),
1494 // wxWidgets will throw a system error and then, we throw another error below, trying to
1495 // add paths to a null watcher. We skip this by installing a temporary log handler that
1496 // catches errors during watcher creation and aborts if any error is detected.
1497 class WatcherLogHandler : public wxLog
1498 {
1499 public:
1500 explicit WatcherLogHandler( bool* err ) :
1501 m_err( err )
1502 {
1503 if( m_err )
1504 *m_err = false;
1505 }
1506
1507 protected:
1508 void DoLogTextAtLevel( wxLogLevel level, const wxString& text ) override
1509 {
1510 if( m_err && ( level == wxLOG_Error || level == wxLOG_FatalError ) )
1511 *m_err = true;
1512 }
1513
1514 private:
1515 bool* m_err;
1516 };
1517
1518 bool watcherHasError = false;
1519 WatcherLogHandler tmpLog( &watcherHasError );
1520 wxLog* oldLog = wxLog::SetActiveTarget( &tmpLog );
1521
1523 m_watcher->SetOwner( this );
1524
1525 // Restore previous log handler
1526 wxLog::SetActiveTarget( oldLog );
1527
1528 if( watcherHasError )
1529 {
1530 return;
1531 }
1532 }
1533
1534 // We can see wxString under a debugger, not a wxFileName
1535 wxFileName fn;
1536 fn.AssignDir( prj_dir );
1537 fn.DontFollowLink();
1538
1539 // Add directories which should be monitored.
1540 // under windows, we add the curr dir and all subdirs
1541 // under unix, we add only the curr dir and the populated subdirs
1542 // see http://docs.wxwidgets.org/trunk/classwx_file_system_watcher.htm
1543 // under unix, the file watcher needs more work to be efficient
1544 // moreover, under wxWidgets 2.9.4, AddTree does not work properly.
1545 {
1546 wxLogNull logNo; // avoid log messages
1547#ifdef __WINDOWS__
1548 if( ! m_watcher->AddTree( fn ) )
1549 {
1550 wxLogTrace( tracePathsAndFiles, "%s: failed to add '%s'\n", __func__,
1551 TO_UTF8( fn.GetFullPath() ) );
1552 return;
1553 }
1554 }
1555#else
1556 if( !m_watcher->Add( fn ) )
1557 {
1558 wxLogTrace( tracePathsAndFiles, "%s: failed to add '%s'\n", __func__,
1559 TO_UTF8( fn.GetFullPath() ) );
1560 return;
1561 }
1562 }
1563
1564 if( m_TreeProject->IsEmpty() )
1565 return;
1566
1567 // Add subdirs
1568 wxTreeItemIdValue cookie;
1569 wxTreeItemId root_id = m_root;
1570
1571 std::stack < wxTreeItemId > subdirs_id;
1572
1573 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1574 int total_watch_count = 0;
1575
1576 while( total_watch_count < ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
1577 {
1578 if( !kid.IsOk() )
1579 {
1580 if( subdirs_id.empty() ) // all items were explored
1581 {
1582 break;
1583 }
1584 else
1585 {
1586 root_id = subdirs_id.top();
1587 subdirs_id.pop();
1588 kid = m_TreeProject->GetFirstChild( root_id, cookie );
1589
1590 if( !kid.IsOk() )
1591 continue;
1592 }
1593 }
1594
1595 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1596
1597 if( itemData && itemData->GetType() == TREE_FILE_TYPE::DIRECTORY )
1598 {
1599 // we can see wxString under a debugger, not a wxFileName
1600 const wxString& path = itemData->GetFileName();
1601
1602 wxLogTrace( tracePathsAndFiles, "%s: add '%s'\n", __func__, TO_UTF8( path ) );
1603
1604 if( wxFileName::IsDirReadable( path ) ) // linux whines about watching protected dir
1605 {
1606 {
1607 // Silence OS errors that come from the watcher
1608 wxLogNull silence;
1609 fn.AssignDir( path );
1610 m_watcher->Add( fn );
1611 total_watch_count++;
1612 }
1613
1614 // if kid is a subdir, push in list to explore it later
1615 if( itemData->IsPopulated() && m_TreeProject->GetChildrenCount( kid ) )
1616 subdirs_id.push( kid );
1617 }
1618 }
1619
1620 kid = m_TreeProject->GetNextChild( root_id, cookie );
1621 }
1622
1623 if( total_watch_count >= ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
1624 wxLogTrace( tracePathsAndFiles, "%s: too many directories to watch\n", __func__ );
1625#endif
1626
1627#if defined(DEBUG) && 1
1628 wxArrayString paths;
1629 m_watcher->GetWatchedPaths( &paths );
1630 wxLogTrace( tracePathsAndFiles, "%s: watched paths:", __func__ );
1631
1632 for( unsigned ii = 0; ii < paths.GetCount(); ii++ )
1633 wxLogTrace( tracePathsAndFiles, " %s\n", TO_UTF8( paths[ii] ) );
1634#endif
1635 }
1636
1637
1639{
1640 // Make sure we don't try to inspect the tree after we've deleted its items.
1642
1643 m_TreeProject->DeleteAllItems();
1644
1645 // Remove the git repository when the project is unloaded
1646 if( m_TreeProject->GetGitRepo() )
1647 {
1648 m_TreeProject->GitCommon()->SetCancelled( true );
1649
1650 // We need to lock the mutex to ensure that no other thread is using the git repository
1651 std::unique_lock<std::mutex> lock( m_TreeProject->GitCommon()->m_gitActionMutex, std::try_to_lock );
1652
1653 if( !lock.owns_lock() )
1654 {
1655 // Block until any in-flight Git actions complete, showing a pulsing progress dialog
1656 {
1657 wxProgressDialog progress( _( "Please wait" ), _( "Waiting for Git operations to finish..." ),
1658 100, this, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_SMOOTH );
1659
1660 // Keep trying to acquire the lock, pulsing the dialog every 100 ms
1661 while ( !lock.try_lock() )
1662 {
1663 progress.Pulse();
1664 std::this_thread::sleep_for( std::chrono::milliseconds(100) );
1665 // allow UI events to process so dialog remains responsive
1666 wxYield();
1667 }
1668 }
1669 }
1670
1671 git_repository* repo = m_TreeProject->GetGitRepo();
1673 m_TreeProject->SetGitRepo( nullptr );
1674 }
1675}
1676
1677
1678void PROJECT_TREE_PANE::onThemeChanged( wxSysColourChangedEvent &aEvent )
1679{
1681 m_TreeProject->LoadIcons();
1682 m_TreeProject->Refresh();
1683
1684 aEvent.Skip();
1685}
1686
1687
1688void PROJECT_TREE_PANE::onPaint( wxPaintEvent& event )
1689{
1690 wxRect rect( wxPoint( 0, 0 ), GetClientSize() );
1691 wxPaintDC dc( this );
1692
1693 dc.SetBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_FRAMEBK ) );
1694 dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_ACTIVEBORDER ), 1 ) );
1695
1696 dc.DrawLine( rect.GetLeft(), rect.GetTop(), rect.GetLeft(), rect.GetBottom() );
1697 dc.DrawLine( rect.GetRight(), rect.GetTop(), rect.GetRight(), rect.GetBottom() );
1698}
1699
1700
1701void KICAD_MANAGER_FRAME::OnChangeWatchedPaths( wxCommandEvent& aEvent )
1702{
1703 m_leftWin->FileWatcherReset();
1704}
1705
1706
1707void PROJECT_TREE_PANE::onGitInitializeProject( wxCommandEvent& aEvent )
1708{
1709 PROJECT_TREE_ITEM* tree_data = GetItemIdData( m_TreeProject->GetRootItem() );
1710
1711 wxString dir = tree_data->GetDir();
1712
1713 if( dir.empty() )
1714 {
1715 wxLogError( "Failed to initialize git project: project directory is empty." );
1716 return;
1717 }
1718
1719 GIT_INIT_HANDLER initHandler( m_TreeProject->GitCommon() );
1720 wxWindow* topLevelParent = wxGetTopLevelParent( this );
1721
1722 if( initHandler.IsRepository( dir ) )
1723 {
1724 DisplayInfoMessage( topLevelParent,
1725 _( "The selected directory is already a Git project." ) );
1726 return;
1727 }
1728
1729 InitResult result = initHandler.InitializeRepository( dir );
1731 {
1732 DisplayErrorMessage( m_parent, _( "Failed to initialize Git project." ),
1733 initHandler.GetErrorString() );
1734 return;
1735 }
1736
1737 m_gitLastError = GIT_ERROR_NONE;
1738
1739 DIALOG_GIT_REPOSITORY dlg( topLevelParent, initHandler.GetRepo() );
1740 dlg.SetTitle( _( "Set default remote" ) );
1741 dlg.SetSkipButtonLabel( _( "Skip" ) );
1742
1743 if( dlg.ShowModal() != wxID_OK )
1744 return;
1745
1746 // Set up the remote
1747 RemoteConfig remoteConfig;
1748 remoteConfig.url = dlg.GetRepoURL();
1749 remoteConfig.username = dlg.GetUsername();
1750 remoteConfig.password = dlg.GetPassword();
1751 remoteConfig.sshKey = dlg.GetRepoSSHPath();
1752 remoteConfig.connType = dlg.GetRepoType();
1753
1754 if( !initHandler.SetupRemote( remoteConfig ) )
1755 {
1756 DisplayErrorMessage( m_parent, _( "Failed to set default remote." ),
1757 initHandler.GetErrorString() );
1758 return;
1759 }
1760
1761 m_gitLastError = GIT_ERROR_NONE;
1762
1763 GIT_PULL_HANDLER handler( m_TreeProject->GitCommon() );
1764 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetch Remote" ), 1, PR_NO_ABORT ) );
1765 handler.PerformFetch();
1766
1770
1774 Prj().GetLocalSettings().m_GitRepoType = "https";
1775 else
1776 Prj().GetLocalSettings().m_GitRepoType = "local";
1777}
1778
1779
1780void PROJECT_TREE_PANE::onGitCompare( wxCommandEvent& aEvent )
1781{
1782
1783}
1784
1785
1786void PROJECT_TREE_PANE::onGitPullProject( wxCommandEvent& aEvent )
1787{
1788 git_repository* repo = m_TreeProject->GetGitRepo();
1789
1790 if( !repo )
1791 return;
1792
1793 GIT_PULL_HANDLER handler( m_TreeProject->GitCommon() );
1794
1795 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetch Remote" ), 1,
1796 PR_NO_ABORT ) );
1797
1798 if( handler.PerformPull() < PullResult::Success )
1799 {
1800 wxString errorMessage = handler.GetErrorString();
1801
1802 DisplayErrorMessage( m_parent, _( "Failed to pull project" ), errorMessage );
1803 }
1804
1805 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1806}
1807
1808
1809void PROJECT_TREE_PANE::onGitPushProject( wxCommandEvent& aEvent )
1810{
1811 git_repository* repo = m_TreeProject->GetGitRepo();
1812
1813 if( !repo )
1814 return;
1815
1816 GIT_PUSH_HANDLER handler( m_TreeProject->GitCommon() );
1817
1818 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetch Remote" ), 1,
1819 PR_NO_ABORT ) );
1820
1821 if( handler.PerformPush() != PushResult::Success )
1822 {
1823 wxString errorMessage = handler.GetErrorString();
1824
1825 DisplayErrorMessage( m_parent, _( "Failed to push project" ), errorMessage );
1826 }
1827
1828 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1829}
1830
1831
1832void PROJECT_TREE_PANE::onGitSwitchBranch( wxCommandEvent& aEvent )
1833{
1834 if( !m_TreeProject->GetGitRepo() )
1835 return;
1836
1837 GIT_BRANCH_HANDLER branchHandler( m_TreeProject->GitCommon() );
1838 wxString branchName;
1839
1840 if( aEvent.GetId() == ID_GIT_SWITCH_BRANCH )
1841 {
1842 DIALOG_GIT_SWITCH dlg( wxGetTopLevelParent( this ), m_TreeProject->GetGitRepo() );
1843
1844 int retval = dlg.ShowModal();
1845 branchName = dlg.GetBranchName();
1846
1847 if( retval == wxID_ADD )
1848 KIGIT::PROJECT_GIT_UTILS::CreateBranch( m_TreeProject->GetGitRepo(), branchName );
1849 else if( retval != wxID_OK )
1850 return;
1851 }
1852 else
1853 {
1854 std::vector<wxString> branches = m_TreeProject->GitCommon()->GetBranchNames();
1855 int branchIndex = aEvent.GetId() - ID_GIT_SWITCH_BRANCH;
1856
1857 if( branchIndex < 0 || static_cast<size_t>( branchIndex ) >= branches.size() )
1858 return;
1859
1860 branchName = branches[branchIndex];
1861 }
1862
1863 wxLogTrace( traceGit, wxS( "onGitSwitchBranch: Switching to branch '%s'" ), branchName );
1864 if( branchHandler.SwitchToBranch( branchName ) != BranchResult::Success )
1865 {
1866 DisplayError( m_parent, branchHandler.GetErrorString() );
1867 }
1868}
1869
1870
1871void PROJECT_TREE_PANE::onGitRemoveVCS( wxCommandEvent& aEvent )
1872{
1873 PROJECT_LOCAL_SETTINGS& localSettings = Prj().GetLocalSettings();
1874
1875 // Toggle the Git integration disabled preference
1876 localSettings.m_GitIntegrationDisabled = !localSettings.m_GitIntegrationDisabled;
1877
1878 wxLogTrace( traceGit, wxS( "onGitRemoveVCS: Git integration %s" ),
1879 localSettings.m_GitIntegrationDisabled ? wxS( "disabled" ) : wxS( "enabled" ) );
1880
1881 if( localSettings.m_GitIntegrationDisabled )
1882 {
1883 // Disabling Git integration - clear the repo reference and item states
1884 m_TreeProject->SetGitRepo( nullptr );
1885 m_gitIconsInitialized = false;
1886
1887 // Clear all item states to remove git status icons
1888 std::stack<wxTreeItemId> items;
1889 items.push( m_TreeProject->GetRootItem() );
1890
1891 while( !items.empty() )
1892 {
1893 wxTreeItemId current = items.top();
1894 items.pop();
1895
1896 m_TreeProject->SetItemState( current, wxTREE_ITEMSTATE_NONE );
1897
1898 wxTreeItemIdValue cookie;
1899 wxTreeItemId child = m_TreeProject->GetFirstChild( current, cookie );
1900
1901 while( child.IsOk() )
1902 {
1903 items.push( child );
1904 child = m_TreeProject->GetNextChild( current, cookie );
1905 }
1906 }
1907 }
1908 else
1909 {
1910 // Re-enabling Git integration - try to find and connect to the repository
1911 wxFileName fn( Prj().GetProjectPath() );
1912 m_TreeProject->SetGitRepo( KIGIT::PROJECT_GIT_UTILS::GetRepositoryForFile( fn.GetPath().c_str() ) );
1913
1914 if( m_TreeProject->GetGitRepo() )
1915 {
1916 m_TreeProject->GitCommon()->SetUsername( localSettings.m_GitRepoUsername );
1917 m_TreeProject->GitCommon()->SetSSHKey( localSettings.m_GitSSHKey );
1918 }
1919 }
1920
1921 // Save the preference to the project local settings file
1922 localSettings.SaveToFile( Prj().GetProjectPath() );
1923}
1924
1925
1927{
1928 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Updating git status icons" ) );
1929 std::unique_lock<std::mutex> lock( m_gitStatusMutex, std::try_to_lock );
1930
1931 if( !lock.owns_lock() )
1932 {
1933 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Failed to acquire lock for git status icon update" ) );
1934 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1935 return;
1936 }
1937
1938 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
1939 {
1940 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Git is disabled or tree control is null" ) );
1941 return;
1942 }
1943
1944 std::stack<wxTreeItemId> items;
1945 items.push(m_TreeProject->GetRootItem());
1946
1947 while( !items.empty() )
1948 {
1949 wxTreeItemId current = items.top();
1950 items.pop();
1951
1952 if( m_TreeProject->ItemHasChildren( current ) )
1953 {
1954 wxTreeItemIdValue cookie;
1955 wxTreeItemId child = m_TreeProject->GetFirstChild( current, cookie );
1956
1957 while( child.IsOk() )
1958 {
1959 items.push( child );
1960
1961 if( auto it = m_gitStatusIcons.find( child ); it != m_gitStatusIcons.end() )
1962 {
1963 m_TreeProject->SetItemState( child, static_cast<int>( it->second ) );
1964 }
1965
1966 child = m_TreeProject->GetNextChild( current, cookie );
1967 }
1968 }
1969 }
1970
1971 if (!m_gitCurrentBranchName.empty())
1972 {
1973 wxTreeItemId kid = m_TreeProject->GetRootItem();
1974 PROJECT_TREE_ITEM* rootItem = GetItemIdData( kid );
1975 wxString filename = wxFileNameFromPath( rootItem->GetFileName() );
1976 m_TreeProject->SetItemText( kid, filename + " [" + m_gitCurrentBranchName + "]" );
1977 m_gitIconsInitialized = true;
1978 }
1979
1980 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Git status icons updated" ) );
1981}
1982
1983
1985{
1986 wxLogTrace( traceGit, wxS( "updateTreeCache: Updating tree cache" ) );
1987
1988 std::unique_lock<std::mutex> lock( m_gitTreeCacheMutex, std::try_to_lock );
1989
1990 if( !lock.owns_lock() )
1991 {
1992 wxLogTrace( traceGit, wxS( "updateTreeCache: Failed to acquire lock for tree cache update" ) );
1993 return;
1994 }
1995
1996 if( !m_TreeProject )
1997 {
1998 wxLogTrace( traceGit, wxS( "updateTreeCache: Tree control is null" ) );
1999 return;
2000 }
2001
2002 wxTreeItemId kid = m_TreeProject->GetRootItem();
2003
2004 if( !kid.IsOk() )
2005 return;
2006
2007 // Collect a map to easily set the state of each item
2008 m_gitTreeCache.clear();
2009 std::stack<wxTreeItemId> items;
2010 items.push( kid );
2011
2012 while( !items.empty() )
2013 {
2014 kid = items.top();
2015 items.pop();
2016
2017 PROJECT_TREE_ITEM* nextItem = GetItemIdData( kid );
2018
2019 if( !nextItem )
2020 continue;
2021
2022 wxString gitAbsPath = nextItem->GetFileName();
2023#ifdef _WIN32
2024 gitAbsPath.Replace( wxS( "\\" ), wxS( "/" ) );
2025#endif
2026 m_gitTreeCache[gitAbsPath] = kid;
2027
2028 wxTreeItemIdValue cookie;
2029 wxTreeItemId child = m_TreeProject->GetFirstChild( kid, cookie );
2030
2031 while( child.IsOk() )
2032 {
2033 items.push( child );
2034 child = m_TreeProject->GetNextChild( kid, cookie );
2035 }
2036 }
2037}
2038
2039
2041{
2042 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Updating git status icons" ) );
2043#if defined( _WIN32 )
2045
2046 if( refresh != 0
2047 && KIPLATFORM::ENV::IsNetworkPath( wxPathOnly( m_Parent->GetProjectFileName() ) ) )
2048 {
2049 // Need to treat windows network paths special here until we get the samba bug fixed
2050 // https://github.com/wxWidgets/wxWidgets/issues/18953
2051 CallAfter(
2052 [this, refresh]()
2053 {
2054 m_gitStatusTimer.Start( refresh, wxTIMER_ONE_SHOT );
2055 } );
2056 }
2057
2058#endif
2059
2060 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
2061 return;
2062
2063 std::unique_lock<std::mutex> lock1( m_gitStatusMutex, std::try_to_lock );
2064 std::unique_lock<std::mutex> lock2( m_gitTreeCacheMutex, std::try_to_lock );
2065
2066 if( !lock1.owns_lock() || !lock2.owns_lock() )
2067 {
2068 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Failed to acquire locks for git status icon update" ) );
2069 return;
2070 }
2071
2072 if( !m_TreeProject->GetGitRepo() )
2073 {
2074 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: No git repository found" ) );
2075 return;
2076 }
2077
2078 // Acquire the git action mutex to synchronize with EmptyTreePrj() shutdown.
2079 // This ensures the repository isn't freed while we're using it.
2080 std::unique_lock<std::mutex> gitLock( m_TreeProject->GitCommon()->m_gitActionMutex, std::try_to_lock );
2081
2082 if( !gitLock.owns_lock() )
2083 {
2084 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Failed to acquire git action mutex" ) );
2085 return;
2086 }
2087
2088 // Check if cancellation was requested (e.g., during shutdown)
2089 if( m_TreeProject->GitCommon()->IsCancelled() )
2090 {
2091 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Cancelled" ) );
2092 return;
2093 }
2094
2095 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
2096
2097 // Set up pathspec for project files
2098 wxFileName rootFilename( Prj().GetProjectFullName() );
2099 wxString repoWorkDir = statusHandler.GetWorkingDirectory();
2100
2101 wxFileName relative = rootFilename;
2102 relative.MakeRelativeTo( repoWorkDir );
2103 wxString pathspecStr = relative.GetPath( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR );
2104
2105#ifdef _WIN32
2106 pathspecStr.Replace( wxS( "\\" ), wxS( "/" ) );
2107#endif
2108
2109 // Get file status
2110 auto fileStatusMap = statusHandler.GetFileStatus( pathspecStr );
2111 auto [localChanges, remoteChanges] = m_TreeProject->GitCommon()->GetDifferentFiles();
2112 statusHandler.UpdateRemoteStatus( localChanges, remoteChanges, fileStatusMap );
2113
2114 bool updated = false;
2115
2116 // Update status icons based on file status
2117 for( const auto& [absPath, fileStatus] : fileStatusMap )
2118 {
2119 auto iter = m_gitTreeCache.find( absPath );
2120 if( iter == m_gitTreeCache.end() )
2121 {
2122 wxLogTrace( traceGit, wxS( "File '%s' not found in tree cache" ), absPath );
2123 continue;
2124 }
2125
2126 auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second, fileStatus.status );
2127 if( inserted || it->second != fileStatus.status )
2128 updated = true;
2129 it->second = fileStatus.status;
2130 }
2131
2132 // Get the current branch name
2134
2135 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Updated git status icons" ) );
2136
2137 // Update UI if icons changed
2138 if( updated || !m_gitIconsInitialized )
2139 {
2140 CallAfter(
2141 [this]()
2142 {
2144 } );
2145 }
2146}
2147
2148
2149void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
2150{
2151 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
2152
2153 git_repository* repo = m_TreeProject->GetGitRepo();
2154
2155 if( repo == nullptr )
2156 {
2157 wxMessageBox( _( "The selected directory is not a Git project." ) );
2158 return;
2159 }
2160
2161 // Get git configuration
2162 GIT_CONFIG_HANDLER configHandler( m_TreeProject->GitCommon() );
2163 GitUserConfig userConfig = configHandler.GetUserConfig();
2164
2165 // Collect modified files in the repository
2166 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
2167 auto fileStatusMap = statusHandler.GetFileStatus();
2168
2169 std::map<wxString, int> modifiedFiles;
2170 std::set<wxString> selected_files;
2171
2172 for( PROJECT_TREE_ITEM* item : tree_data )
2173 {
2174 if( item->GetType() != TREE_FILE_TYPE::DIRECTORY )
2175 selected_files.emplace( item->GetFileName() );
2176 }
2177
2178 wxString repoWorkDir = statusHandler.GetWorkingDirectory();
2179
2180 for( const auto& [absPath, fileStatus] : fileStatusMap )
2181 {
2182 // Skip current, conflicted, or ignored files
2183 if( fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT
2185 || fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_IGNORED )
2186 {
2187 continue;
2188 }
2189
2190 wxFileName fn( absPath );
2191
2192 // Convert to relative path for the modifiedFiles map
2193 wxString relativePath = absPath;
2194 if( relativePath.StartsWith( repoWorkDir ) )
2195 {
2196 relativePath = relativePath.Mid( repoWorkDir.length() );
2197#ifdef _WIN32
2198 relativePath.Replace( wxS( "\\" ), wxS( "/" ) );
2199#endif
2200 }
2201
2202 // Do not commit files outside the project directory
2203 wxString projectPath = Prj().GetProjectPath();
2204 if( !absPath.StartsWith( projectPath ) )
2205 continue;
2206
2207 // Skip lock files
2208 if( fn.GetExt().CmpNoCase( FILEEXT::LockFileExtension ) == 0 )
2209 continue;
2210
2211 // Skip autosave, lock, and backup files
2212 if( fn.GetName().StartsWith( FILEEXT::LockFilePrefix )
2213 || fn.GetName().EndsWith( FILEEXT::BackupFileSuffix ) )
2214 {
2215 continue;
2216 }
2217
2218 // Skip archived project backups
2219 if( fn.GetPath().Contains( Prj().GetProjectName() + wxT( "-backups" ) ) )
2220 continue;
2221
2222 if( aEvent.GetId() == ID_GIT_COMMIT_PROJECT )
2223 {
2224 modifiedFiles.emplace( relativePath, fileStatus.gitStatus );
2225 }
2226 else if( selected_files.count( absPath ) )
2227 {
2228 modifiedFiles.emplace( relativePath, fileStatus.gitStatus );
2229 }
2230 }
2231
2232 // Create a commit dialog
2233 DIALOG_GIT_COMMIT dlg( wxGetTopLevelParent( this ), repo, userConfig.authorName, userConfig.authorEmail,
2234 modifiedFiles );
2235 auto ret = dlg.ShowModal();
2236
2237 if( ret != wxID_OK )
2238 return;
2239
2240 std::vector<wxString> files = dlg.GetSelectedFiles();
2241
2242 if( dlg.GetCommitMessage().IsEmpty() )
2243 {
2244 wxMessageBox( _( "Discarding commit due to empty commit message." ) );
2245 return;
2246 }
2247
2248 if( files.empty() )
2249 {
2250 wxMessageBox( _( "Discarding commit due to empty file selection." ) );
2251 return;
2252 }
2253
2254 GIT_COMMIT_HANDLER commitHandler( repo );
2255 auto result = commitHandler.PerformCommit( files, dlg.GetCommitMessage(),
2256 dlg.GetAuthorName(), dlg.GetAuthorEmail() );
2257
2259 {
2260 wxMessageBox( wxString::Format( _( "Failed to create commit: %s" ),
2261 commitHandler.GetErrorString() ) );
2262 return;
2263 }
2264
2265 wxLogTrace( traceGit, wxS( "Created commit" ) );
2266 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
2267}
2268
2269
2270void PROJECT_TREE_PANE::onGitAddToIndex( wxCommandEvent& aEvent )
2271{
2272
2273}
2274
2275
2276bool PROJECT_TREE_PANE::canFileBeAddedToVCS( const wxString& aFile )
2277{
2278 if( !m_TreeProject->GetGitRepo() )
2279 return false;
2280
2281 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
2282 auto fileStatusMap = statusHandler.GetFileStatus();
2283
2284 // Check if file is already tracked or staged
2285 for( const auto& [filePath, fileStatus] : fileStatusMap )
2286 {
2287 if( filePath.EndsWith( aFile ) || filePath == aFile )
2288 {
2289 // File can be added if it's untracked
2290 return fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_UNTRACKED;
2291 }
2292 }
2293
2294 // If file not found in status, it might be addable
2295 return true;
2296}
2297
2298
2299void PROJECT_TREE_PANE::onGitSyncProject( wxCommandEvent& aEvent )
2300{
2301 wxLogTrace( traceGit, "Syncing project" );
2302 git_repository* repo = m_TreeProject->GetGitRepo();
2303
2304 if( !repo )
2305 {
2306 wxLogTrace( traceGit, "sync: No git repository found" );
2307 return;
2308 }
2309
2310 GIT_SYNC_HANDLER handler( repo );
2311 handler.PerformSync();
2312}
2313
2314
2315void PROJECT_TREE_PANE::onGitFetch( wxCommandEvent& aEvent )
2316{
2317 KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon();
2318
2319 if( !gitCommon )
2320 return;
2321
2322 GIT_PULL_HANDLER handler( gitCommon );
2323 handler.PerformFetch();
2324
2325 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
2326}
2327
2328
2329void PROJECT_TREE_PANE::onGitResolveConflict( wxCommandEvent& aEvent )
2330{
2331 git_repository* repo = m_TreeProject->GetGitRepo();
2332
2333 if( !repo )
2334 return;
2335
2336 GIT_RESOLVE_CONFLICT_HANDLER handler( repo );
2337 handler.PerformResolveConflict();
2338}
2339
2340
2341void PROJECT_TREE_PANE::onGitRevertLocal( wxCommandEvent& aEvent )
2342{
2343 git_repository* repo = m_TreeProject->GetGitRepo();
2344
2345 if( !repo )
2346 return;
2347
2348 GIT_REVERT_HANDLER handler( repo );
2349 handler.PerformRevert();
2350}
2351
2352
2353void PROJECT_TREE_PANE::onGitRemoveFromIndex( wxCommandEvent& aEvent )
2354{
2355 git_repository* repo = m_TreeProject->GetGitRepo();
2356
2357 if( !repo )
2358 return;
2359
2360 GIT_REMOVE_FROM_INDEX_HANDLER handler( repo );
2361 handler.PerformRemoveFromIndex();
2362}
2363
2364
2366{
2367
2368}
2369
2370
2371void PROJECT_TREE_PANE::onGitSyncTimer( wxTimerEvent& aEvent )
2372{
2373 wxLogTrace( traceGit, "onGitSyncTimer" );
2374 COMMON_SETTINGS::GIT& gitSettings = Pgm().GetCommonSettings()->m_Git;
2375
2376 if( !gitSettings.enableGit || !m_TreeProject )
2377 return;
2378
2380
2381 tp.detach_task( [this]()
2382 {
2383 KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon();
2384
2385 if( !gitCommon )
2386 {
2387 wxLogTrace( traceGit, "onGitSyncTimer: No git repository found" );
2388 return;
2389 }
2390
2391 // Check if cancellation was requested (e.g., during shutdown)
2392 if( gitCommon->IsCancelled() )
2393 {
2394 wxLogTrace( traceGit, "onGitSyncTimer: Cancelled" );
2395 return;
2396 }
2397
2398 GIT_PULL_HANDLER handler( gitCommon );
2399 handler.PerformFetch();
2400
2401 // Only schedule the follow-up work if not cancelled
2402 if( !gitCommon->IsCancelled() )
2403 CallAfter( [this]() { gitStatusTimerHandler(); } );
2404 } );
2405
2406 if( gitSettings.updatInterval > 0 )
2407 {
2408 wxLogTrace( traceGit, "onGitSyncTimer: Restarting git sync timer" );
2409 // We store the timer interval in minutes but wxTimer uses milliseconds
2410 m_gitSyncTimer.Start( gitSettings.updatInterval * 60 * 1000, wxTIMER_ONE_SHOT );
2411 }
2412}
2413
2414
2416{
2417 // Check if git is still available and not cancelled before spawning background work
2418 KIGIT_COMMON* gitCommon = m_TreeProject ? m_TreeProject->GitCommon() : nullptr;
2419
2420 if( !gitCommon || gitCommon->IsCancelled() )
2421 return;
2422
2425
2426 tp.detach_task( [this]() { updateGitStatusIconMap(); } );
2427}
2428
2429void PROJECT_TREE_PANE::onGitStatusTimer( wxTimerEvent& aEvent )
2430{
2431 wxLogTrace( traceGit, "onGitStatusTimer" );
2432
2433 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
2434 return;
2435
2437}
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