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