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