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