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