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