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 (C) 1992-2023 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 <git2.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
35#include <advanced_config.h>
36#include <bitmaps.h>
37#include <bitmap_store.h>
38#include <confirm.h>
41#include <gestfich.h>
42#include <macros.h>
43#include <trace_helpers.h>
46#include <core/kicad_algo.h>
47#include <paths.h>
49#include <scoped_set_reset.h>
50#include <string_utils.h>
51#include <launch_ext.h>
52#include <wx/dcclient.h>
53#include <wx/settings.h>
54
68
69
71
72#include "project_tree_item.h"
73#include "project_tree.h"
74#include "pgm_kicad.h"
75#include "kicad_id.h"
76#include "kicad_manager_frame.h"
77
78#include "project_tree_pane.h"
79
80
81/* Note about the project tree build process:
82 * Building the project tree can be *very* long if there are a lot of subdirectories in the
83 * working directory. Unfortunately, this happens easily if the project file *.pro is in the
84 * user's home directory.
85 * So the tree project is built "on demand":
86 * First the tree is built from the current directory and shows files and subdirs.
87 * > First level subdirs trees are built (i.e subdirs contents are not read)
88 * > When expanding a subdir, each subdir contains is read, and the corresponding sub tree is
89 * populated on the fly.
90 */
91
92// list of files extensions listed in the tree project window
93// Add extensions in a compatible regex format to see others files types
94static const wxChar* s_allowedExtensionsToList[] = {
95 wxT( "^.*\\.pro$" ),
96 wxT( "^.*\\.kicad_pro$" ),
97 wxT( "^.*\\.pdf$" ),
98 wxT( "^.*\\.sch$" ), // Legacy Eeschema files
99 wxT( "^.*\\.kicad_sch$" ), // S-expr Eeschema files
100 wxT( "^[^$].*\\.brd$" ), // Legacy Pcbnew files
101 wxT( "^[^$].*\\.kicad_pcb$" ), // S format Pcbnew board files
102 wxT( "^[^$].*\\.kicad_dru$" ), // Design rule files
103 wxT( "^[^$].*\\.kicad_wks$" ), // S format kicad drawing sheet files
104 wxT( "^[^$].*\\.kicad_mod$" ), // S format kicad footprint files, currently not listed
105 wxT( "^.*\\.net$" ), // pcbnew netlist file
106 wxT( "^.*\\.cir$" ), // Spice netlist file
107 wxT( "^.*\\.lib$" ), // Legacy schematic library file
108 wxT( "^.*\\.kicad_sym$" ), // S-expr symbol libraries
109 wxT( "^.*\\.txt$" ), // Text files
110 wxT( "^.*\\.md$" ), // Markdown files
111 wxT( "^.*\\.pho$" ), // Gerber file (Old Kicad extension)
112 wxT( "^.*\\.gbr$" ), // Gerber file
113 wxT( "^.*\\.gbrjob$" ), // Gerber job file
114 wxT( "^.*\\.gb[alops]$" ), // Gerber back (or bottom) layer file (deprecated Protel ext)
115 wxT( "^.*\\.gt[alops]$" ), // Gerber front (or top) layer file (deprecated Protel ext)
116 wxT( "^.*\\.g[0-9]{1,2}$" ), // Gerber inner layer file (deprecated Protel ext)
117 wxT( "^.*\\.gm[0-9]{1,2}$" ), // Gerber mechanical layer file (deprecated Protel ext)
118 wxT( "^.*\\.gko$" ), // Gerber keepout layer file (deprecated Protel ext)
119 wxT( "^.*\\.odt$" ),
120 wxT( "^.*\\.htm$" ),
121 wxT( "^.*\\.html$" ),
122 wxT( "^.*\\.rpt$" ), // Report files
123 wxT( "^.*\\.csv$" ), // Report files in comma separated format
124 wxT( "^.*\\.pos$" ), // Footprint position files
125 wxT( "^.*\\.cmp$" ), // CvPcb cmp/footprint link files
126 wxT( "^.*\\.drl$" ), // Excellon drill files
127 wxT( "^.*\\.nc$" ), // Excellon NC drill files (alternate file ext)
128 wxT( "^.*\\.xnc$" ), // Excellon NC drill files (alternate file ext)
129 wxT( "^.*\\.svg$" ), // SVG print/plot files
130 wxT( "^.*\\.ps$" ), // PostScript plot files
131 wxT( "^.*\\.zip$" ), // Zip archive files
132 nullptr // end of list
133};
134
135
143BEGIN_EVENT_TABLE( PROJECT_TREE_PANE, wxSashLayoutWindow )
144 EVT_TREE_ITEM_ACTIVATED( ID_PROJECT_TREE, PROJECT_TREE_PANE::onSelect )
145 EVT_TREE_ITEM_EXPANDED( ID_PROJECT_TREE, PROJECT_TREE_PANE::onExpand )
146 EVT_TREE_ITEM_RIGHT_CLICK( ID_PROJECT_TREE, PROJECT_TREE_PANE::onRight )
153
168
169
170 EVT_IDLE( PROJECT_TREE_PANE::onIdle )
171 EVT_PAINT( PROJECT_TREE_PANE::onPaint )
172END_EVENT_TABLE()
173
174
176 wxSashLayoutWindow( parent, ID_LEFT_FRAME, wxDefaultPosition, wxDefaultSize,
177 wxNO_BORDER | wxTAB_TRAVERSAL )
178{
179 m_Parent = parent;
180 m_TreeProject = nullptr;
181 m_isRenaming = false;
182 m_selectedItem = nullptr;
183 m_watcherNeedReset = false;
184 m_lastGitStatusUpdate = wxDateTime::Now();
185 m_gitLastError = GIT_ERROR_NONE;
186
187 m_watcher = nullptr;
188 Connect( wxEVT_FSWATCHER,
189 wxFileSystemWatcherEventHandler( PROJECT_TREE_PANE::onFileSystemEvent ) );
190
191 Bind( wxEVT_SYS_COLOUR_CHANGED,
192 wxSysColourChangedEventHandler( PROJECT_TREE_PANE::onThemeChanged ), this );
193
194 /*
195 * Filtering is now inverted: the filters are actually used to _enable_ support
196 * for a given file type.
197 */
198 for( int ii = 0; s_allowedExtensionsToList[ii] != nullptr; ii++ )
199 m_filters.emplace_back( s_allowedExtensionsToList[ii] );
200
201 m_filters.emplace_back( wxT( "^no KiCad files found" ) );
202
203 ReCreateTreePrj();
204
205}
206
207
209{
211}
212
213
215{
216 if( m_watcher )
217 {
218 m_watcher->RemoveAll();
219 m_watcher->SetOwner( nullptr );
220 delete m_watcher;
221 m_watcher = nullptr;
222 }
223}
224
225
227{
228 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
229
230 if( tree_data.size() != 1 )
231 return;
232
233 wxString prj_filename = tree_data[0]->GetFileName();
234
235 m_Parent->LoadProject( prj_filename );
236}
237
238
239void PROJECT_TREE_PANE::onOpenDirectory( wxCommandEvent& event )
240{
241 // Get the root directory name:
242 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
243
244 for( PROJECT_TREE_ITEM* item_data : tree_data )
245 {
246 // Ask for the new sub directory name
247 wxString curr_dir = item_data->GetDir();
248
249 if( curr_dir.IsEmpty() )
250 {
251 // Use project path if the tree view path was empty.
252 curr_dir = wxPathOnly( m_Parent->GetProjectFileName() );
253
254 // As a last resort use the user's documents folder.
255 if( curr_dir.IsEmpty() || !wxFileName::DirExists( curr_dir ) )
257
258 if( !curr_dir.IsEmpty() )
259 curr_dir += wxFileName::GetPathSeparator();
260 }
261
262 LaunchExternal( curr_dir );
263 }
264}
265
266
267void PROJECT_TREE_PANE::onCreateNewDirectory( wxCommandEvent& event )
268{
269 // Get the root directory name:
270 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
271
272 for( PROJECT_TREE_ITEM* item_data : tree_data )
273 {
274 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
275
276 // Ask for the new sub directory name
277 wxString curr_dir = item_data->GetDir();
278
279 if( curr_dir.IsEmpty() )
280 curr_dir = prj_dir;
281
282 wxString new_dir = wxGetTextFromUser( _( "Directory name:" ), _( "Create New Directory" ) );
283
284 if( new_dir.IsEmpty() )
285 return;
286
287 wxString full_dirname = curr_dir + wxFileName::GetPathSeparator() + new_dir;
288
289 if( !wxMkdir( full_dirname ) )
290 return;
291
292 addItemToProjectTree( full_dirname, item_data->GetId(), nullptr, false );
293 }
294}
295
296
298{
299 switch( type )
300 {
301 case TREE_FILE_TYPE::LEGACY_PROJECT: return LegacyProjectFileExtension;
302 case TREE_FILE_TYPE::JSON_PROJECT: return ProjectFileExtension;
303 case TREE_FILE_TYPE::LEGACY_SCHEMATIC: return LegacySchematicFileExtension;
304 case TREE_FILE_TYPE::SEXPR_SCHEMATIC: return KiCadSchematicFileExtension;
305 case TREE_FILE_TYPE::LEGACY_PCB: return LegacyPcbFileExtension;
306 case TREE_FILE_TYPE::SEXPR_PCB: return KiCadPcbFileExtension;
307 case TREE_FILE_TYPE::GERBER: return GerberFileExtensionsRegex;
308 case TREE_FILE_TYPE::GERBER_JOB_FILE: return GerberJobFileExtension;
309 case TREE_FILE_TYPE::HTML: return HtmlFileExtension;
310 case TREE_FILE_TYPE::PDF: return PdfFileExtension;
311 case TREE_FILE_TYPE::TXT: return TextFileExtension;
312 case TREE_FILE_TYPE::MD: return MarkdownFileExtension;
313 case TREE_FILE_TYPE::NET: return NetlistFileExtension;
314 case TREE_FILE_TYPE::NET_SPICE: return SpiceFileExtension;
315 case TREE_FILE_TYPE::CMP_LINK: return FootprintAssignmentFileExtension;
316 case TREE_FILE_TYPE::REPORT: return ReportFileExtension;
317 case TREE_FILE_TYPE::FP_PLACE: return FootprintPlaceFileExtension;
318 case TREE_FILE_TYPE::DRILL: return DrillFileExtension;
319 case TREE_FILE_TYPE::DRILL_NC: return "nc";
320 case TREE_FILE_TYPE::DRILL_XNC: return "xnc";
321 case TREE_FILE_TYPE::SVG: return SVGFileExtension;
322 case TREE_FILE_TYPE::DRAWING_SHEET: return DrawingSheetFileExtension;
323 case TREE_FILE_TYPE::FOOTPRINT_FILE: return KiCadFootprintFileExtension;
324 case TREE_FILE_TYPE::SCHEMATIC_LIBFILE: return LegacySymbolLibFileExtension;
325 case TREE_FILE_TYPE::SEXPR_SYMBOL_LIB_FILE: return KiCadSymbolLibFileExtension;
326 case TREE_FILE_TYPE::DESIGN_RULES: return DesignRulesFileExtension;
327 case TREE_FILE_TYPE::ZIP_ARCHIVE: return ArchiveFileExtension;
328
329 case TREE_FILE_TYPE::ROOT:
330 case TREE_FILE_TYPE::UNKNOWN:
331 case TREE_FILE_TYPE::MAX:
332 case TREE_FILE_TYPE::DIRECTORY: break;
333 }
334
335 return wxEmptyString;
336}
337
338
339std::vector<wxString> getProjects( const wxDir& dir )
340{
341 std::vector<wxString> projects;
342 wxString dir_filename;
343 bool haveFile = dir.GetFirst( &dir_filename );
344
345 while( haveFile )
346 {
347 wxFileName file( dir_filename );
348
349 if( file.GetExt() == LegacyProjectFileExtension || file.GetExt() == ProjectFileExtension )
350 projects.push_back( file.GetName() );
351
352 haveFile = dir.GetNext( &dir_filename );
353 }
354
355 return projects;
356}
357
358static git_repository* get_git_repository_for_file( const char* filename )
359{
360 git_repository* repo = nullptr;
361 git_buf repo_path = GIT_BUF_INIT;
362
363 // Find the repository path for the given file
364 if( git_repository_discover( &repo_path, filename, 0, NULL ) )
365 {
366 #if 0
367 printf( "get_git_repository_for_file: %s\n", git_error_last()->message ); fflush( 0 );
368 #endif
369 return nullptr;
370 }
371
372 if( git_repository_open( &repo, repo_path.ptr ) )
373 {
374 git_buf_free( &repo_path );
375 return nullptr;
376 }
377
378 // Free the git_buf memory
379 git_buf_free( &repo_path );
380
381 return repo;
382}
383
384
385wxTreeItemId PROJECT_TREE_PANE::addItemToProjectTree( const wxString& aName,
386 const wxTreeItemId& aParent,
387 std::vector<wxString>* aProjectNames,
388 bool aRecurse )
389{
390 TREE_FILE_TYPE type = TREE_FILE_TYPE::UNKNOWN;
391 wxFileName fn( aName );
392
393#ifndef __WXMSW__
394 // Files/dirs names starting by "." are not visible files under unices (including MacOS),
395 // but are under MSW.
396 if( fn.GetName().StartsWith( wxT( "." ) ) )
397 return wxTreeItemId();
398#endif
399
400 if( wxDirExists( aName ) )
401 {
402 type = TREE_FILE_TYPE::DIRECTORY;
403 }
404 else
405 {
406 // Filter
407 wxRegEx reg;
408 bool addFile = false;
409
410 for( const wxString& m_filter : m_filters )
411 {
412 wxCHECK2_MSG( reg.Compile( m_filter, wxRE_ICASE ), continue,
413 wxString::Format( "Regex %s failed to compile.", m_filter ) );
414
415 if( reg.Matches( aName ) )
416 {
417 addFile = true;
418 break;
419 }
420 }
421
422 if( !addFile )
423 return wxTreeItemId();
424
425 for( int i = static_cast<int>( TREE_FILE_TYPE::LEGACY_PROJECT );
426 i < static_cast<int>( TREE_FILE_TYPE::MAX ); i++ )
427 {
428 wxString ext = GetFileExt( (TREE_FILE_TYPE) i );
429
430 if( ext == wxT( "" ) )
431 continue;
432
433 reg.Compile( wxString::FromAscii( "^.*\\." ) + ext + wxString::FromAscii( "$" ),
434 wxRE_ICASE );
435
436 if( reg.Matches( aName ) )
437 {
438 type = (TREE_FILE_TYPE) i;
439 break;
440 }
441 }
442 }
443
444 wxString file = wxFileNameFromPath( aName );
445 wxFileName currfile( file );
446 wxFileName project( m_Parent->GetProjectFileName() );
447
448 // Ignore legacy projects with the same name as the current project
449 if( ( type == TREE_FILE_TYPE::LEGACY_PROJECT )
450 && ( currfile.GetName().CmpNoCase( project.GetName() ) == 0 ) )
451 {
452 return wxTreeItemId();
453 }
454
455 if( currfile.GetExt() == GetFileExt( TREE_FILE_TYPE::LEGACY_SCHEMATIC )
456 || currfile.GetExt() == GetFileExt( TREE_FILE_TYPE::SEXPR_SCHEMATIC ) )
457 {
458 if( aProjectNames )
459 {
460 if( !alg::contains( *aProjectNames, currfile.GetName() ) )
461 return wxTreeItemId();
462 }
463 else
464 {
465 PROJECT_TREE_ITEM* parentTreeItem = GetItemIdData( aParent );
466 wxDir parentDir( parentTreeItem->GetDir() );
467 std::vector<wxString> projects = getProjects( parentDir );
468
469 if( !alg::contains( projects, currfile.GetName() ) )
470 return wxTreeItemId();
471 }
472 }
473
474 // also check to see if it is already there.
475 wxTreeItemIdValue cookie;
476 wxTreeItemId kid = m_TreeProject->GetFirstChild( aParent, cookie );
477
478 while( kid.IsOk() )
479 {
480 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
481
482 if( itemData && itemData->GetFileName() == aName )
483 return itemData->GetId(); // well, we would have added it, but it is already here!
484
485 kid = m_TreeProject->GetNextChild( aParent, cookie );
486 }
487
488 // Only show current files if both legacy and current files are present
489 if( type == TREE_FILE_TYPE::LEGACY_PROJECT || type == TREE_FILE_TYPE::JSON_PROJECT
490 || type == TREE_FILE_TYPE::LEGACY_SCHEMATIC || type == TREE_FILE_TYPE::SEXPR_SCHEMATIC )
491 {
492 kid = m_TreeProject->GetFirstChild( aParent, cookie );
493
494 while( kid.IsOk() )
495 {
496 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
497
498 if( itemData )
499 {
500 wxFileName fname( itemData->GetFileName() );
501
502 if( fname.GetName().CmpNoCase( currfile.GetName() ) == 0 )
503 {
504 switch( type )
505 {
506 case TREE_FILE_TYPE::LEGACY_PROJECT:
507 if( itemData->GetType() == TREE_FILE_TYPE::JSON_PROJECT )
508 return wxTreeItemId();
509
510 break;
511
512 case TREE_FILE_TYPE::LEGACY_SCHEMATIC:
513 if( itemData->GetType() == TREE_FILE_TYPE::SEXPR_SCHEMATIC )
514 return wxTreeItemId();
515
516 break;
517
518 case TREE_FILE_TYPE::JSON_PROJECT:
519 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_PROJECT )
520 m_TreeProject->Delete( kid );
521
522 break;
523
524 case TREE_FILE_TYPE::SEXPR_SCHEMATIC:
525 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_SCHEMATIC )
526 m_TreeProject->Delete( kid );
527
528 break;
529
530 default:
531 break;
532 }
533 }
534 }
535
536 kid = m_TreeProject->GetNextChild( aParent, cookie );
537 }
538 }
539
540 // Append the item (only appending the filename not the full path):
541 wxTreeItemId newItemId = m_TreeProject->AppendItem( aParent, file );
542 PROJECT_TREE_ITEM* data = new PROJECT_TREE_ITEM( type, aName, m_TreeProject );
543
544 m_TreeProject->SetItemData( newItemId, data );
545 data->SetState( 0 );
546
547 // Mark root files (files which have the same aName as the project)
548 wxString fileName = currfile.GetName().Lower();
549 wxString projName = project.GetName().Lower();
550
551 if( fileName == projName || fileName.StartsWith( projName + "-" ) )
552 data->SetRootFile( true );
553
554#ifndef __WINDOWS__
555 bool subdir_populated = false;
556#endif
557
558 // This section adds dirs and files found in the subdirs
559 // in this case AddFile is recursive, but for the first level only.
560 if( TREE_FILE_TYPE::DIRECTORY == type && aRecurse )
561 {
562 wxDir dir( aName );
563
564 if( dir.IsOpened() ) // protected dirs will not open properly.
565 {
566 std::vector<wxString> projects = getProjects( dir );
567 wxString dir_filename;
568 bool haveFile = dir.GetFirst( &dir_filename );
569
570 data->SetPopulated( true );
571
572#ifndef __WINDOWS__
573 subdir_populated = aRecurse;
574#endif
575
576 while( haveFile )
577 {
578 // Add name in tree, but do not recurse
579 wxString path = aName + wxFileName::GetPathSeparator() + dir_filename;
580 addItemToProjectTree( path, newItemId, &projects, false );
581
582 haveFile = dir.GetNext( &dir_filename );
583 }
584 }
585
586 // Sort filenames by alphabetic order
587 m_TreeProject->SortChildren( newItemId );
588 }
589
590#ifndef __WINDOWS__
591 if( subdir_populated )
592 m_watcherNeedReset = true;
593#endif
594
595 return newItemId;
596}
597
598
600{
601 wxString pro_dir = m_Parent->GetProjectFileName();
602
603 if( !m_TreeProject )
604 m_TreeProject = new PROJECT_TREE( this );
605 else
606 m_TreeProject->DeleteAllItems();
607
608 if( !pro_dir ) // This is empty from PROJECT_TREE_PANE constructor
609 return;
610
612 {
613 git_repository_free( m_TreeProject->GetGitRepo() );
614 m_TreeProject->SetGitRepo( nullptr );
615 }
616
617 wxFileName fn = pro_dir;
618 bool prjReset = false;
619
620 if( !fn.IsOk() )
621 {
622 fn.Clear();
623 fn.SetPath( PATHS::GetDefaultUserProjectsPath() );
624 fn.SetName( NAMELESS_PROJECT );
625 fn.SetExt( ProjectFileExtension );
626 prjReset = true;
627 }
628
629 bool prjOpened = fn.FileExists();
630
631 // Bind the git repository to the project tree (if it exists)
632 m_TreeProject->SetGitRepo( get_git_repository_for_file( fn.GetPath().c_str() ) );
633 m_TreeProject->GitCommon()->SetPassword( Prj().GetLocalSettings().m_GitRepoPassword );
634 m_TreeProject->GitCommon()->SetUsername( Prj().GetLocalSettings().m_GitRepoUsername );
635 m_TreeProject->GitCommon()->SetSSHKey( Prj().GetLocalSettings().m_GitSSHKey );
636
637 wxString conn_type = Prj().GetLocalSettings().m_GitRepoType;
638
639 if( conn_type == "https" )
641 else if( conn_type == "ssh" )
643 else
645
646 // We may have opened a legacy project, in which case GetProjectFileName will return the
647 // name of the migrated (new format) file, which may not have been saved to disk yet.
648 if( !prjOpened && !prjReset )
649 {
650 fn.SetExt( LegacyProjectFileExtension );
651 prjOpened = fn.FileExists();
652
653 // Set the ext back so that in the tree view we see the (not-yet-saved) new file
654 fn.SetExt( ProjectFileExtension );
655 }
656
657 // root tree:
658 m_root = m_TreeProject->AddRoot( fn.GetFullName(), static_cast<int>( TREE_FILE_TYPE::ROOT ),
659 static_cast<int>( TREE_FILE_TYPE::ROOT ) );
660 m_TreeProject->SetItemBold( m_root, true );
661
662 // The main project file is now a JSON file
663 PROJECT_TREE_ITEM* data = new PROJECT_TREE_ITEM( TREE_FILE_TYPE::JSON_PROJECT,
664 fn.GetFullPath(), m_TreeProject );
665
666 m_TreeProject->SetItemData( m_root, data );
667
668 // Now adding all current files if available
669 if( prjOpened )
670 {
671 pro_dir = wxPathOnly( m_Parent->GetProjectFileName() );
672 wxDir dir( pro_dir );
673
674 if( dir.IsOpened() ) // protected dirs will not open, see "man opendir()"
675 {
676 std::vector<wxString> projects = getProjects( dir );
677 wxString filename;
678 bool haveFile = dir.GetFirst( &filename );
679
680 while( haveFile )
681 {
682 if( filename != fn.GetFullName() )
683 {
684 wxString name = dir.GetName() + wxFileName::GetPathSeparator() + filename;
685 // Add items living in the project directory, and populate the item
686 // if it is a directory (sub directories will be not populated)
687 addItemToProjectTree( name, m_root, &projects, true );
688 }
689
690 haveFile = dir.GetNext( &filename );
691 }
692 }
693 }
694 else
695 {
696 m_TreeProject->AppendItem( m_root, wxT( "Empty project" ) );
697 }
698
699 m_TreeProject->Expand( m_root );
700
701 // Sort filenames by alphabetic order
702 m_TreeProject->SortChildren( m_root );
704}
705
706
708{
709 git_repository* repo = m_TreeProject->GetGitRepo();
710
711 if( !repo )
712 return false;
713
714 git_status_options opts = GIT_STATUS_OPTIONS_INIT;
715 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
716 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX
717 | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
718
719 git_status_list* status_list = nullptr;
720 int error = git_status_list_new( &status_list, repo, &opts );
721
722 if( error != GIT_OK )
723 return false;
724
725 bool has_changed_files = git_status_list_entrycount( status_list ) > 0;
726 git_status_list_free( status_list );
727 return has_changed_files;
728}
729
730
731void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event )
732{
733 wxTreeItemId curr_item = Event.GetItem();
734
735 // Ensure item is selected (Under Windows right click does not select the item)
736 m_TreeProject->SelectItem( curr_item );
737
738 std::vector<PROJECT_TREE_ITEM*> selection = GetSelectedData();
739
740 bool can_switch_to_project = true;
741 bool can_create_new_directory = true;
742 bool can_open_this_directory = true;
743 bool can_edit = true;
744 bool can_rename = true;
745 bool can_delete = true;
746
747 bool vcs_has_repo = m_TreeProject->GetGitRepo() != nullptr;
748 bool vcs_can_commit = hasChangedFiles();
749 bool vcs_can_init = !vcs_has_repo;
750 bool vcs_can_remove = vcs_has_repo;
751 bool vcs_can_fetch = vcs_has_repo && m_TreeProject->GitCommon()->HasPushAndPullRemote();
752 bool vcs_can_push = vcs_can_fetch && m_TreeProject->GitCommon()->HasLocalCommits();
753 bool vcs_can_pull = vcs_can_fetch;
754 bool vcs_can_switch = vcs_has_repo;
755 bool vcs_menu = ADVANCED_CFG::GetCfg().m_EnableGit;
756
757 // Check if the libgit2 library has been successfully initialized
758 int major, minor, rev;
759 bool libgit_init = ( git_libgit2_version( &major, &minor, &rev ) == GIT_OK );
760
761 vcs_menu &= libgit_init;
762
763 if( selection.size() == 0 )
764 return;
765
766 // Remove things that don't make sense for multiple selections
767 if( selection.size() != 1 )
768 {
769 can_switch_to_project = false;
770 can_create_new_directory = false;
771 can_rename = false;
772 }
773
774 for( PROJECT_TREE_ITEM* item : selection )
775 {
776 // Check for empty project
777 if( !item )
778 {
779 can_switch_to_project = false;
780 can_edit = false;
781 can_rename = false;
782 continue;
783 }
784
785 can_delete = item->CanDelete();
786 can_rename = item->CanRename();
787
788 switch( item->GetType() )
789 {
790 case TREE_FILE_TYPE::JSON_PROJECT:
791 case TREE_FILE_TYPE::LEGACY_PROJECT:
792 can_rename = false;
793
794 if( item->GetId() == m_TreeProject->GetRootItem() )
795 {
796 can_switch_to_project = false;
797 }
798 else
799 {
800 can_create_new_directory = false;
801 can_open_this_directory = false;
802 }
803 break;
804
805 case TREE_FILE_TYPE::DIRECTORY:
806 can_switch_to_project = false;
807 can_edit = false;
808 break;
809
810 case TREE_FILE_TYPE::SEXPR_SCHEMATIC:
811 case TREE_FILE_TYPE::SEXPR_PCB:
813
814 default:
815 can_switch_to_project = false;
816 can_create_new_directory = false;
817 can_open_this_directory = false;
818
819 break;
820 }
821 }
822
823 wxMenu popup_menu;
824 wxString text;
825 wxString help_text;
826
827 if( can_switch_to_project )
828 {
829 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_SWITCH_TO_OTHER, _( "Switch to this Project" ),
830 _( "Close all editors, and switch to the selected project" ),
831 KiBitmap( BITMAPS::open_project ) );
832 popup_menu.AppendSeparator();
833 }
834
835 if( can_create_new_directory )
836 {
837 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_NEWDIR, _( "New Directory..." ),
838 _( "Create a New Directory" ), KiBitmap( BITMAPS::directory ) );
839 }
840
841 if( can_open_this_directory )
842 {
843 if( selection.size() == 1 )
844 {
845#ifdef __APPLE__
846 text = _( "Reveal in Finder" );
847 help_text = _( "Reveals the directory in a Finder window" );
848#else
849 text = _( "Open Directory in File Explorer" );
850 help_text = _( "Opens the directory in the default system file manager" );
851#endif
852 }
853 else
854 {
855#ifdef __APPLE__
856 text = _( "Reveal in Finder" );
857 help_text = _( "Reveals the directories in a Finder window" );
858#else
859 text = _( "Open Directories in File Explorer" );
860 help_text = _( "Opens the directories in the default system file manager" );
861#endif
862 }
863
864 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_OPEN_DIR, text, help_text,
865 KiBitmap( BITMAPS::directory_browser ) );
866 }
867
868 if( can_edit )
869 {
870 if( selection.size() == 1 )
871 help_text = _( "Open the file in a Text Editor" );
872 else
873 help_text = _( "Open files in a Text Editor" );
874
875 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_TXTEDIT, _( "Edit in a Text Editor" ), help_text,
876 KiBitmap( BITMAPS::editor ) );
877 }
878
879 if( can_rename )
880 {
881 if( selection.size() == 1 )
882 {
883 text = _( "Rename File..." );
884 help_text = _( "Rename file" );
885 }
886 else
887 {
888 text = _( "Rename Files..." );
889 help_text = _( "Rename files" );
890 }
891
892 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_RENAME, text, help_text,
893 KiBitmap( BITMAPS::right ) );
894 }
895
896 if( can_delete )
897 {
898 if( selection.size() == 1 )
899 help_text = _( "Delete the file and its content" );
900 else
901 help_text = _( "Delete the files and their contents" );
902
903 if( can_switch_to_project
904 || can_create_new_directory
905 || can_open_this_directory
906 || can_edit
907 || can_rename )
908 {
909 popup_menu.AppendSeparator();
910 }
911
912#ifdef __WINDOWS__
913 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Delete" ), help_text,
914 KiBitmap( BITMAPS::trash ) );
915#else
916 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Move to Trash" ), help_text,
917 KiBitmap( BITMAPS::trash ) );
918#endif
919 }
920
921 if( vcs_menu )
922 {
923 wxMenu* vcs_submenu = new wxMenu();
924 wxMenu* branch_submenu = new wxMenu();
925 wxMenuItem* vcs_menuitem = nullptr;
926
927 vcs_menuitem = vcs_submenu->Append( ID_GIT_INITIALIZE_PROJECT,
928 _( "Add Project to Version Control..." ),
929 _( "Initialize a new repository" ) );
930 vcs_menuitem->Enable( vcs_can_init );
931
932
933 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_PROJECT, _( "Commit Project..." ),
934 _( "Commit changes to the local repository" ) );
935 vcs_menuitem->Enable( vcs_can_commit );
936
937 vcs_menuitem = vcs_submenu->Append( ID_GIT_PUSH, _( "Push" ),
938 _( "Push committed local changes to remote repository" ) );
939 vcs_menuitem->Enable( vcs_can_push );
940
941 vcs_menuitem = vcs_submenu->Append( ID_GIT_PULL, _( "Pull" ),
942 _( "Pull changes from remote repository into local" ) );
943 vcs_menuitem->Enable( vcs_can_pull );
944
945 vcs_submenu->AppendSeparator();
946
947 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_FILE, _( "Commit File..." ),
948 _( "Commit changes to the local repository" ) );
949 vcs_menuitem->Enable( vcs_can_commit );
950
951 vcs_submenu->AppendSeparator();
952
953 // vcs_menuitem = vcs_submenu->Append( ID_GIT_COMPARE, _( "Diff" ),
954 // _( "Show changes between the repository and working tree" ) );
955 // vcs_menuitem->Enable( vcs_can_diff );
956
957 std::vector<wxString> branchNames = m_TreeProject->GitCommon()->GetBranchNames();
958
959 // Skip the first one (that is the current branch)
960 for( size_t ii = 1; ii < branchNames.size() && ii < 6; ++ii )
961 {
962 wxString msg = _( "Switch to branch " ) + branchNames[ii];
963 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH + ii, branchNames[ii], msg );
964 vcs_menuitem->Enable( vcs_can_switch );
965 }
966
967 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Other..." ),
968 _( "Switch to a different branch" ) );
969 vcs_menuitem->Enable( vcs_can_switch );
970
971 vcs_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Switch to Branch" ), branch_submenu );
972
973 vcs_submenu->AppendSeparator();
974
975 vcs_menuitem = vcs_submenu->Append( ID_GIT_REMOVE_VCS, _( "Remove Version Control" ),
976 _( "Delete all version control files from the project directory." ) );
977 vcs_menuitem->Enable( vcs_can_remove );
978
979 popup_menu.AppendSeparator();
980 popup_menu.AppendSubMenu( vcs_submenu, _( "Version Control" ) );
981 }
982
983 if( popup_menu.GetMenuItemCount() > 0 )
984 PopupMenu( &popup_menu );
985}
986
987
989{
990 wxString editorname = Pgm().GetTextEditor();
991
992 if( editorname.IsEmpty() )
993 {
994 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
995 return;
996 }
997
998 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
999 wxString files;
1000
1001 for( PROJECT_TREE_ITEM* item_data : tree_data )
1002 {
1003 wxString fullFileName = item_data->GetFileName();
1004
1005 if( !files.IsEmpty() )
1006 files += " ";
1007
1008 files += fullFileName;
1009 }
1010
1011 ExecuteFile( editorname, files );
1012}
1013
1014
1015void PROJECT_TREE_PANE::onDeleteFile( wxCommandEvent& event )
1016{
1017 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1018
1019 for( PROJECT_TREE_ITEM* item_data : tree_data )
1020 item_data->Delete();
1021}
1022
1023
1024void PROJECT_TREE_PANE::onRenameFile( wxCommandEvent& event )
1025{
1026 wxTreeItemId curr_item = m_TreeProject->GetFocusedItem();
1027 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1028
1029 // XXX: Unnecessary?
1030 if( tree_data.size() != 1 )
1031 return;
1032
1033 wxString buffer = m_TreeProject->GetItemText( curr_item );
1034 wxString msg = wxString::Format( _( "Change filename: '%s'" ),
1035 tree_data[0]->GetFileName() );
1036 wxTextEntryDialog dlg( this, msg, _( "Change filename" ), buffer );
1037
1038 if( dlg.ShowModal() != wxID_OK )
1039 return; // canceled by user
1040
1041 buffer = dlg.GetValue();
1042 buffer.Trim( true );
1043 buffer.Trim( false );
1044
1045 if( buffer.IsEmpty() )
1046 return; // empty file name not allowed
1047
1048 tree_data[0]->Rename( buffer, true );
1049 m_isRenaming = true;
1050}
1051
1052
1053void PROJECT_TREE_PANE::onSelect( wxTreeEvent& Event )
1054{
1055 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1056
1057 if( tree_data.size() != 1 )
1058 return;
1059
1060 // Bookmark the selected item but don't try and activate it until later. If we do it now,
1061 // there will be more events at least on Windows in this frame that will steal focus from
1062 // any newly launched windows
1063 m_selectedItem = tree_data[0];
1064}
1065
1066
1067void PROJECT_TREE_PANE::onIdle( wxIdleEvent& aEvent )
1068{
1069 // Idle executes once all other events finished processing. This makes it ideal to launch
1070 // a new window without starting Focus wars.
1071 if( m_watcherNeedReset )
1072 {
1073 m_selectedItem = nullptr;
1075 }
1076
1077 if( m_selectedItem != nullptr )
1078 {
1079 // Activate launches a window which may run the event loop on top of us and cause OnIdle
1080 // to get called again, so be sure to block off the activation condition first.
1082 m_selectedItem = nullptr;
1083
1084 item->Activate( this );
1085 }
1086
1087 // Inside this routine, we rate limit to once per 2 seconds
1089}
1090
1091
1092void PROJECT_TREE_PANE::onExpand( wxTreeEvent& Event )
1093{
1094 wxTreeItemId itemId = Event.GetItem();
1095 PROJECT_TREE_ITEM* tree_data = GetItemIdData( itemId );
1096
1097 if( !tree_data )
1098 return;
1099
1100 if( tree_data->GetType() != TREE_FILE_TYPE::DIRECTORY )
1101 return;
1102
1103 // explore list of non populated subdirs, and populate them
1104 wxTreeItemIdValue cookie;
1105 wxTreeItemId kid = m_TreeProject->GetFirstChild( itemId, cookie );
1106
1107#ifndef __WINDOWS__
1108 bool subdir_populated = false;
1109#endif
1110
1111 for( ; kid.IsOk(); kid = m_TreeProject->GetNextChild( itemId, cookie ) )
1112 {
1113 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1114
1115 if( !itemData || itemData->GetType() != TREE_FILE_TYPE::DIRECTORY )
1116 continue;
1117
1118 if( itemData->IsPopulated() )
1119 continue;
1120
1121 wxString fileName = itemData->GetFileName();
1122 wxDir dir( fileName );
1123
1124 if( dir.IsOpened() )
1125 {
1126 std::vector<wxString> projects = getProjects( dir );
1127 wxString dir_filename;
1128 bool haveFile = dir.GetFirst( &dir_filename );
1129
1130 while( haveFile )
1131 {
1132 // Add name to tree item, but do not recurse in subdirs:
1133 wxString name = fileName + wxFileName::GetPathSeparator() + dir_filename;
1134 addItemToProjectTree( name, kid, &projects, false );
1135
1136 haveFile = dir.GetNext( &dir_filename );
1137 }
1138
1139 itemData->SetPopulated( true ); // set state to populated
1140
1141#ifndef __WINDOWS__
1142 subdir_populated = true;
1143#endif
1144 }
1145
1146 // Sort filenames by alphabetic order
1147 m_TreeProject->SortChildren( kid );
1148 }
1149
1150#ifndef __WINDOWS__
1151 if( subdir_populated )
1152 m_watcherNeedReset = true;
1153#endif
1154}
1155
1156
1157std::vector<PROJECT_TREE_ITEM*> PROJECT_TREE_PANE::GetSelectedData()
1158{
1159 wxArrayTreeItemIds selection;
1160 std::vector<PROJECT_TREE_ITEM*> data;
1161
1162 m_TreeProject->GetSelections( selection );
1163
1164 for( auto it = selection.begin(); it != selection.end(); it++ )
1165 {
1166 PROJECT_TREE_ITEM* item = GetItemIdData( *it );
1167 if( !item )
1168 {
1169 wxLogDebug( "Null tree item returned for selection, dynamic_cast failed?" );
1170 continue;
1171 }
1172
1173 data.push_back( item );
1174 }
1175
1176 return data;
1177}
1178
1179
1181{
1182 return dynamic_cast<PROJECT_TREE_ITEM*>( m_TreeProject->GetItemData( aId ) );
1183}
1184
1185
1186wxTreeItemId PROJECT_TREE_PANE::findSubdirTreeItem( const wxString& aSubDir )
1187{
1188 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1189
1190 // If the subdir is the current working directory, return m_root
1191 // in main list:
1192 if( prj_dir == aSubDir )
1193 return m_root;
1194
1195 // The subdir is in the main tree or in a subdir: Locate it
1196 wxTreeItemIdValue cookie;
1197 wxTreeItemId root_id = m_root;
1198 std::stack<wxTreeItemId> subdirs_id;
1199
1200 wxTreeItemId child = m_TreeProject->GetFirstChild( root_id, cookie );
1201
1202 while( true )
1203 {
1204 if( ! child.IsOk() )
1205 {
1206 if( subdirs_id.empty() ) // all items were explored
1207 {
1208 root_id = child; // Not found: return an invalid wxTreeItemId
1209 break;
1210 }
1211 else
1212 {
1213 root_id = subdirs_id.top();
1214 subdirs_id.pop();
1215 child = m_TreeProject->GetFirstChild( root_id, cookie );
1216
1217 if( !child.IsOk() )
1218 continue;
1219 }
1220 }
1221
1222 PROJECT_TREE_ITEM* itemData = GetItemIdData( child );
1223
1224 if( itemData && ( itemData->GetType() == TREE_FILE_TYPE::DIRECTORY ) )
1225 {
1226 if( itemData->GetFileName() == aSubDir ) // Found!
1227 {
1228 root_id = child;
1229 break;
1230 }
1231
1232 // child is a subdir, push in list to explore it later
1233 if( itemData->IsPopulated() )
1234 subdirs_id.push( child );
1235 }
1236
1237 child = m_TreeProject->GetNextChild( root_id, cookie );
1238 }
1239
1240 return root_id;
1241}
1242
1243
1244void PROJECT_TREE_PANE::onFileSystemEvent( wxFileSystemWatcherEvent& event )
1245{
1246 // No need to process events when we're shutting down
1247 if( !m_watcher )
1248 return;
1249
1250 const wxFileName& pathModified = event.GetPath();
1251 wxString subdir = pathModified.GetPath();
1252 wxString fn = pathModified.GetFullPath();
1253
1254 switch( event.GetChangeType() )
1255 {
1256 case wxFSW_EVENT_DELETE:
1257 case wxFSW_EVENT_CREATE:
1258 case wxFSW_EVENT_RENAME:
1260 break;
1261
1262 case wxFSW_EVENT_MODIFY:
1265 case wxFSW_EVENT_ACCESS:
1266 default:
1267 return;
1268 }
1269
1270 wxTreeItemId root_id = findSubdirTreeItem( subdir );
1271
1272 if( !root_id.IsOk() )
1273 return;
1274
1275 wxTreeItemIdValue cookie; // dummy variable needed by GetFirstChild()
1276 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1277
1278 switch( event.GetChangeType() )
1279 {
1280 case wxFSW_EVENT_CREATE:
1281 {
1282 wxTreeItemId newitem =
1283 addItemToProjectTree( pathModified.GetFullPath(), root_id, nullptr, true );
1284
1285 // If we are in the process of renaming a file, select the new one
1286 // This is needed for MSW and OSX, since we don't get RENAME events from them, just a
1287 // pair of DELETE and CREATE events.
1288 if( m_isRenaming && newitem.IsOk() )
1289 {
1290 m_TreeProject->SelectItem( newitem );
1291 m_isRenaming = false;
1292 }
1293 }
1294 break;
1295
1296 case wxFSW_EVENT_DELETE:
1297 while( kid.IsOk() )
1298 {
1299 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1300
1301 if( itemData && itemData->GetFileName() == fn )
1302 {
1303 m_TreeProject->Delete( kid );
1304 return;
1305 }
1306 kid = m_TreeProject->GetNextChild( root_id, cookie );
1307 }
1308 break;
1309
1310 case wxFSW_EVENT_RENAME :
1311 {
1312 const wxFileName& newpath = event.GetNewPath();
1313 wxString newdir = newpath.GetPath();
1314 wxString newfn = newpath.GetFullPath();
1315
1316 while( kid.IsOk() )
1317 {
1318 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1319
1320 if( itemData && itemData->GetFileName() == fn )
1321 {
1322 m_TreeProject->Delete( kid );
1323 break;
1324 }
1325
1326 kid = m_TreeProject->GetNextChild( root_id, cookie );
1327 }
1328
1329 // Add the new item only if it is not the current project file (root item).
1330 // Remember: this code is called by a wxFileSystemWatcherEvent event, and not always
1331 // called after an actual file rename, and the cleanup code does not explore the
1332 // root item, because it cannot be renamed by the user. Also, ensure the new file
1333 // actually exists on the file system before it is readded. On Linux, moving a file
1334 // to the trash can cause the same path to be returned in both the old and new paths
1335 // of the event, even though the file isn't there anymore.
1336 PROJECT_TREE_ITEM* rootData = GetItemIdData( root_id );
1337
1338 if( rootData && newpath.Exists() && ( newfn != rootData->GetFileName() ) )
1339 {
1340 wxTreeItemId newroot_id = findSubdirTreeItem( newdir );
1341 wxTreeItemId newitem = addItemToProjectTree( newfn, newroot_id, nullptr, true );
1342
1343 // If the item exists, select it
1344 if( newitem.IsOk() )
1345 m_TreeProject->SelectItem( newitem );
1346 }
1347
1348 m_isRenaming = false;
1349 }
1350 break;
1351 }
1352
1353 // Sort filenames by alphabetic order
1354 m_TreeProject->SortChildren( root_id );
1355}
1356
1357
1359{
1360 m_watcherNeedReset = false;
1361
1362 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1363
1364#if defined( _WIN32 )
1365 if( KIPLATFORM::ENV::IsNetworkPath( prj_dir ) )
1366 {
1367 // Due to a combination of a bug in SAMBA sending bad change event IDs and wxWidgets
1368 // choosing to fault on an invalid event ID instead of sanely ignoring them we need to
1369 // avoid spawning a filewatcher. Unfortunately this punishes corporate environments with
1370 // Windows Server shares :/
1371 m_Parent->SetStatusText( _( "Network path: not monitoring folder changes" ), 1 );
1372 return;
1373 }
1374 else
1375 {
1376 m_Parent->SetStatusText( _( "Local path: monitoring folder changes" ), 1 );
1377 }
1378#endif
1379
1380 // Prepare file watcher:
1381 if( m_watcher )
1382 {
1383 m_watcher->RemoveAll();
1384 }
1385 else
1386 {
1387 m_watcher = new wxFileSystemWatcher();
1388 m_watcher->SetOwner( this );
1389 }
1390
1391 // We can see wxString under a debugger, not a wxFileName
1392 wxFileName fn;
1393 fn.AssignDir( prj_dir );
1394 fn.DontFollowLink();
1395
1396 // Add directories which should be monitored.
1397 // under windows, we add the curr dir and all subdirs
1398 // under unix, we add only the curr dir and the populated subdirs
1399 // see http://docs.wxwidgets.org/trunk/classwx_file_system_watcher.htm
1400 // under unix, the file watcher needs more work to be efficient
1401 // moreover, under wxWidgets 2.9.4, AddTree does not work properly.
1402#ifdef __WINDOWS__
1403 m_watcher->AddTree( fn );
1404#else
1405 m_watcher->Add( fn );
1406
1407 if( m_TreeProject->IsEmpty() )
1408 return;
1409
1410 // Add subdirs
1411 wxTreeItemIdValue cookie;
1412 wxTreeItemId root_id = m_root;
1413
1414 std::stack < wxTreeItemId > subdirs_id;
1415
1416 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1417
1418 while( true )
1419 {
1420 if( !kid.IsOk() )
1421 {
1422 if( subdirs_id.empty() ) // all items were explored
1423 {
1424 break;
1425 }
1426 else
1427 {
1428 root_id = subdirs_id.top();
1429 subdirs_id.pop();
1430 kid = m_TreeProject->GetFirstChild( root_id, cookie );
1431
1432 if( !kid.IsOk() )
1433 continue;
1434 }
1435 }
1436
1437 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1438
1439 if( itemData && itemData->GetType() == TREE_FILE_TYPE::DIRECTORY )
1440 {
1441 // we can see wxString under a debugger, not a wxFileName
1442 const wxString& path = itemData->GetFileName();
1443
1444 wxLogTrace( tracePathsAndFiles, "%s: add '%s'\n", __func__, TO_UTF8( path ) );
1445
1446 if( wxFileName::IsDirReadable( path ) ) // linux whines about watching protected dir
1447 {
1448 fn.AssignDir( path );
1449 m_watcher->Add( fn );
1450
1451 // if kid is a subdir, push in list to explore it later
1452 if( itemData->IsPopulated() && m_TreeProject->GetChildrenCount( kid ) )
1453 subdirs_id.push( kid );
1454 }
1455 }
1456
1457 kid = m_TreeProject->GetNextChild( root_id, cookie );
1458 }
1459#endif
1460
1461#if defined(DEBUG) && 1
1462 wxArrayString paths;
1463 m_watcher->GetWatchedPaths( &paths );
1464 wxLogTrace( tracePathsAndFiles, "%s: watched paths:", __func__ );
1465
1466 for( unsigned ii = 0; ii < paths.GetCount(); ii++ )
1467 wxLogTrace( tracePathsAndFiles, " %s\n", TO_UTF8( paths[ii] ) );
1468#endif
1469}
1470
1471
1473{
1474 // Make sure we don't try to inspect the tree after we've deleted its items.
1476
1477 m_TreeProject->DeleteAllItems();
1478}
1479
1480
1481void PROJECT_TREE_PANE::onThemeChanged( wxSysColourChangedEvent &aEvent )
1482{
1485 m_TreeProject->Refresh();
1486
1487 aEvent.Skip();
1488}
1489
1490
1491void PROJECT_TREE_PANE::onPaint( wxPaintEvent& event )
1492{
1493 wxRect rect( wxPoint( 0, 0 ), GetClientSize() );
1494 wxPaintDC dc( this );
1495
1496 dc.SetBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_FRAMEBK ) );
1497 dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_ACTIVEBORDER ), 1 ) );
1498
1499 dc.DrawLine( rect.GetLeft(), rect.GetTop(), rect.GetLeft(), rect.GetBottom() );
1500 dc.DrawLine( rect.GetRight(), rect.GetTop(), rect.GetRight(), rect.GetBottom() );
1501}
1502
1503
1504void KICAD_MANAGER_FRAME::OnChangeWatchedPaths( wxCommandEvent& aEvent )
1505{
1507}
1508
1509
1510void PROJECT_TREE_PANE::onGitInitializeProject( wxCommandEvent& aEvent )
1511{
1512 PROJECT_TREE_ITEM* tree_data = GetItemIdData( m_TreeProject->GetRootItem() );
1513
1514 wxString dir = tree_data->GetDir();
1515
1516 if( dir.empty() )
1517 {
1518 wxLogError( "Failed to initialize git project: project directory is empty." );
1519 return;
1520 }
1521
1522 // Check if the directory is already a git repository
1523 git_repository* repo = nullptr;
1524 int error = git_repository_open(&repo, dir.mb_str());
1525
1526 if( error == 0 )
1527 {
1528 // Directory is already a git repository
1529
1530 DisplayInfoMessage( this, _( "The selected directory is already a git project." ) );
1531 git_repository_free( repo );
1532 return;
1533 }
1534 else
1535 {
1536 // Directory is not a git repository
1537 error = git_repository_init( &repo, dir.mb_str(), 0 );
1538
1539 if( error != 0 )
1540 {
1541 git_repository_free( repo );
1542
1543 if( m_gitLastError != git_error_last()->klass )
1544 {
1545 m_gitLastError = git_error_last()->klass;
1546 DisplayErrorMessage( this, _( "Failed to initialize git project." ),
1547 git_error_last()->message );
1548 }
1549
1550 return;
1551 }
1552 else
1553 {
1554 m_TreeProject->SetGitRepo( repo );
1555 m_gitLastError = GIT_ERROR_NONE;
1556 }
1557 }
1558
1559 DIALOG_GIT_REPOSITORY dlg( this, repo );
1560
1561 dlg.SetTitle( _( "Set default remote" ) );
1562
1563 if( dlg.ShowModal() != wxID_OK )
1564 return;
1565
1566 //Set up the git remote
1567
1572
1573 git_remote* remote = nullptr;
1574 wxString fullURL;
1575
1577 {
1578 fullURL = dlg.GetUsername() + "@" + dlg.GetRepoURL();
1579 }
1581 {
1582 fullURL = dlg.GetRepoURL().StartsWith( "https" ) ? "https://" : "http://";
1583
1584 if( !dlg.GetUsername().empty() )
1585 {
1586 fullURL.append( dlg.GetUsername() );
1587
1588 if( !dlg.GetPassword().empty() )
1589 {
1590 fullURL.append( wxS( ":" ) );
1591 fullURL.append( dlg.GetPassword() );
1592 }
1593
1594 fullURL.append( wxS( "@" ) );
1595 }
1596
1597 fullURL.append( dlg.GetBareRepoURL() );
1598 }
1599 else
1600 {
1601 fullURL = dlg.GetRepoURL();
1602 }
1603
1604
1605 error = git_remote_create_with_fetchspec( &remote, repo, "origin",
1606 fullURL.ToStdString().c_str(),
1607 "+refs/heads/*:refs/remotes/origin/*" );
1608
1609 if( error != GIT_OK )
1610 {
1611 if( m_gitLastError != git_error_last()->klass )
1612 {
1613 m_gitLastError = git_error_last()->klass;
1614 DisplayErrorMessage( this, _( "Failed to set default remote." ),
1615 git_error_last()->message );
1616 }
1617
1618 return;
1619 }
1620
1621 m_gitLastError = GIT_ERROR_NONE;
1622
1623 GIT_PULL_HANDLER handler( repo );
1624
1628 handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() );
1629
1630 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetching Remote" ), 1 ) );
1631
1632 handler.PerformFetch();
1633
1637
1641 Prj().GetLocalSettings().m_GitRepoType = "https";
1642 else
1643 Prj().GetLocalSettings().m_GitRepoType = "local";
1644}
1645
1646
1647void PROJECT_TREE_PANE::onGitCompare( wxCommandEvent& aEvent )
1648{
1649
1650}
1651
1652
1653void PROJECT_TREE_PANE::onGitPullProject( wxCommandEvent& aEvent )
1654{
1655 git_repository* repo = m_TreeProject->GetGitRepo();
1656
1657 if( !repo )
1658 return;
1659
1660 GIT_PULL_HANDLER handler( repo );
1661
1665 handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() );
1666
1667 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetching Remote" ), 1 ) );
1668
1669 handler.PerformPull();
1670}
1671
1672
1673void PROJECT_TREE_PANE::onGitPushProject( wxCommandEvent& aEvent )
1674{
1675 git_repository* repo = m_TreeProject->GetGitRepo();
1676
1677 if( !repo )
1678 return;
1679
1680 GIT_PUSH_HANDLER handler( repo );
1681
1685 handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() );
1686
1687 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetching Remote" ), 1 ) );
1688
1689 if( handler.PerformPush() != PushResult::Success )
1690 {
1691 wxString errorMessage = handler.GetErrorString();
1692
1693 DisplayErrorMessage( this, _( "Failed to push project" ), errorMessage );
1694 }
1695}
1696
1697
1698static int git_create_branch( git_repository* aRepo, wxString& aBranchName )
1699{
1700 git_oid head_oid;
1701
1702 if( int error = git_reference_name_to_id( &head_oid, aRepo, "HEAD" ) != 0 )
1703 {
1704 wxLogError( "Failed to lookup HEAD reference" );
1705 return error;
1706 }
1707
1708 // Lookup the current commit object
1709 git_commit* commit = nullptr;
1710 if( int error = git_commit_lookup( &commit, aRepo, &head_oid ) != GIT_OK )
1711 {
1712 wxLogError( "Failed to lookup commit" );
1713 return error;
1714 }
1715
1716 git_reference* branchRef = nullptr;
1717
1718 if( git_branch_create( &branchRef, aRepo, aBranchName.mb_str(), commit, 0 ) != 0 )
1719 {
1720 wxLogError( "Failed to create branch" );
1721 git_commit_free( commit );
1722 return -1;
1723 }
1724
1725 git_commit_free( commit );
1726 git_reference_free( branchRef );
1727
1728 return 0;
1729}
1730
1731
1732void PROJECT_TREE_PANE::onGitSwitchBranch( wxCommandEvent& aEvent )
1733{
1734 git_repository* repo = m_TreeProject->GetGitRepo();
1735
1736 if( !repo )
1737 return;
1738
1739 DIALOG_GIT_SWITCH dlg( this, repo );
1740
1741 int retval = dlg.ShowModal();
1742 wxString branchName = dlg.GetBranchName();
1743
1744 if( retval == wxID_ADD )
1745 git_create_branch( repo, branchName);
1746 else if( retval != wxID_OK )
1747 return;
1748
1749 // Retrieve the reference to the existing branch using libgit2
1750 git_reference* branchRef = nullptr;
1751
1752 if( git_reference_lookup( &branchRef, repo, branchName.mb_str() ) != GIT_OK &&
1753 git_reference_dwim( &branchRef, repo, branchName.mb_str() ) != GIT_OK )
1754 {
1755 wxString errorMessage = wxString::Format( _( "Failed to lookup branch '%s': %s" ), branchName, giterr_last()->message );
1756 DisplayError( this, errorMessage );
1757 return;
1758 }
1759
1760 const char* branchRefName = git_reference_name( branchRef );
1761
1762 git_object* branchObj = nullptr;
1763
1764 if( git_revparse_single( &branchObj, repo, branchName.mb_str() ) != 0 )
1765 {
1766 wxString errorMessage =
1767 wxString::Format( _( "Failed to find branch head for '%s'" ), branchName );
1768 DisplayError( this, errorMessage );
1769 git_reference_free( branchRef );
1770 return;
1771 }
1772
1773
1774 // Switch to the branch
1775 if( git_checkout_tree( repo, branchObj, nullptr ) != 0 )
1776 {
1777 wxString errorMessage =
1778 wxString::Format( _( "Failed to switch to branch '%s'" ), branchName );
1779 DisplayError( this, errorMessage );
1780 git_reference_free( branchRef );
1781 git_object_free( branchObj );
1782 return;
1783 }
1784
1785 // Update the HEAD reference
1786 if( git_repository_set_head( repo, branchRefName ) != 0 )
1787 {
1788 wxString errorMessage = wxString::Format(
1789 _( "Failed to update HEAD reference for branch '%s'" ), branchName );
1790 DisplayError( this, errorMessage );
1791 git_reference_free( branchRef );
1792 git_object_free( branchObj );
1793 return;
1794 }
1795
1796 // Free resources
1797 git_reference_free( branchRef );
1798 git_object_free( branchObj );
1799}
1800
1801
1802void PROJECT_TREE_PANE::onGitRemoveVCS( wxCommandEvent& aEvent )
1803{
1804 git_repository* repo = m_TreeProject->GetGitRepo();
1805
1806 if( !repo
1807 || !IsOK( this, _( "Are you sure you want to remove git tracking from this project?" ) ) )
1808 {
1809 return;
1810 }
1811
1812 // Remove the VCS (git) from the project directory
1813 git_repository_free( repo );
1814 m_TreeProject->SetGitRepo( nullptr );
1815
1816 // Remove the .git directory
1817 wxFileName fn( m_Parent->GetProjectFileName() );
1818 fn.AppendDir( ".git" );
1819
1820 wxString errors;
1821
1822 if( !RmDirRecursive( fn.GetPath(), &errors ) )
1823 {
1824 DisplayErrorMessage( this, _( "Failed to remove git directory" ), errors );
1825 }
1826
1827 // Clear all item states
1828 std::stack<wxTreeItemId> items;
1829 items.push( m_TreeProject->GetRootItem() );
1830
1831 while( !items.empty() )
1832 {
1833 wxTreeItemId current = items.top();
1834 items.pop();
1835
1836 // Process the current item
1837 m_TreeProject->SetItemState( current, wxTREE_ITEMSTATE_NONE );
1838
1839 wxTreeItemIdValue cookie;
1840 wxTreeItemId child = m_TreeProject->GetFirstChild( current, cookie );
1841
1842 while( child.IsOk() )
1843 {
1844 items.push( child );
1845 child = m_TreeProject->GetNextChild( current, cookie );
1846 }
1847 }
1848}
1849
1850
1852{
1853 if( ADVANCED_CFG::GetCfg().m_EnableGit == false )
1854 return;
1855
1856 if( !m_TreeProject )
1857 return;
1858
1859 wxTimeSpan timeSinceLastUpdate = wxDateTime::Now() - m_lastGitStatusUpdate;
1860
1861 if( timeSinceLastUpdate.Abs() < wxTimeSpan::Seconds( 2 ) )
1862 return;
1863
1864 m_lastGitStatusUpdate = wxDateTime::Now();
1865
1866 wxTreeItemId kid = m_TreeProject->GetRootItem();
1867
1868 if( !kid.IsOk() )
1869 return;
1870
1871 git_repository* repo = m_TreeProject->GetGitRepo();
1872
1873 if( !repo )
1874 return;
1875
1876 // Get Current Branch
1877
1878 git_reference* currentBranchReference = nullptr;
1879 git_repository_head( &currentBranchReference, repo );
1880
1881 // Get the current branch name
1882 if( currentBranchReference )
1883 {
1884 PROJECT_TREE_ITEM* rootItem = GetItemIdData( kid );
1885 wxString filename = wxFileNameFromPath( rootItem->GetFileName() );
1886 wxString branchName = git_reference_shorthand( currentBranchReference );
1887
1888 m_TreeProject->SetItemText( kid, filename + " [" + branchName + "]" );
1889 git_reference_free( currentBranchReference );
1890 }
1891 else
1892 {
1893 if( giterr_last()->klass != m_gitLastError )
1894 wxLogError( "Failed to lookup current branch: %s", giterr_last()->message );
1895
1896 m_gitLastError = giterr_last()->klass;
1897 }
1898
1899 // Collect a map to easily set the state of each item
1900 std::map<wxString, wxTreeItemId> branchMap;
1901 {
1902 std::stack<wxTreeItemId> items;
1903 items.push( kid );
1904
1905 while( !items.empty() )
1906 {
1907 kid = items.top();
1908 items.pop();
1909
1910 PROJECT_TREE_ITEM* nextItem = GetItemIdData( kid );
1911
1912 branchMap[nextItem->GetFileName()] = kid;
1913
1914 wxTreeItemIdValue cookie;
1915 wxTreeItemId child = m_TreeProject->GetFirstChild( kid, cookie );
1916
1917 while( child.IsOk() )
1918 {
1919 items.push( child );
1920 child = m_TreeProject->GetNextChild( kid, cookie );
1921 }
1922 }
1923 }
1924
1925 git_status_options status_options = GIT_STATUS_OPTIONS_INIT;
1926 status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1927 status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
1928
1929 git_index* index = nullptr;
1930
1931 if( git_repository_index( &index, repo ) != GIT_OK )
1932 {
1933 m_gitLastError = giterr_last()->klass;
1934 wxLogDebug( "Failed to get git index: %s", giterr_last()->message );
1935 return;
1936 }
1937
1938 git_status_list* status_list = nullptr;
1939
1940 if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK )
1941 {
1942 wxLogDebug( "Failed to get git status list: %s", giterr_last()->message );
1943 git_index_free( index );
1944 return;
1945 }
1946
1947 auto [ localChanges, remoteChanges ] = m_TreeProject->GitCommon()->GetDifferentFiles();
1948
1949 size_t count = git_status_list_entrycount( status_list );
1950
1951 for( size_t ii = 0; ii < count; ++ii )
1952 {
1953 const git_status_entry* entry = git_status_byindex( status_list, ii );
1954 std::string path( entry->head_to_index? entry->head_to_index->old_file.path
1955 : entry->index_to_workdir->old_file.path );
1956 wxFileName fn( path );
1957 fn.MakeAbsolute( git_repository_workdir( repo ) );
1958
1959 auto iter = branchMap.find( fn.GetFullPath() );
1960
1961 if( iter == branchMap.end() )
1962 continue;
1963
1964 // If we are current, don't continue because we still need to check to see if the
1965 // current commit is ahead/behind the remote. If the file is modified/added/deleted,
1966 // that is the main status we want to show.
1967 if( entry->status == GIT_STATUS_CURRENT )
1968 {
1969 m_TreeProject->SetItemState(
1970 iter->second,
1972 }
1973 else if( entry->status & ( GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED ) )
1974 {
1975 m_TreeProject->SetItemState(
1976 iter->second,
1978 continue;
1979 }
1980 else if( entry->status & ( GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW ) )
1981 {
1982 m_TreeProject->SetItemState(
1983 iter->second,
1984 static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_ADDED ) );
1985 continue;
1986 }
1987 else if( entry->status & ( GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED ) )
1988 {
1989 m_TreeProject->SetItemState(
1990 iter->second,
1992 continue;
1993 }
1994
1995 // Check if file is up to date with the remote
1996 if( localChanges.count( path ) )
1997 {
1998 m_TreeProject->SetItemState(
1999 iter->second,
2000 static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD ) );
2001 continue;
2002 }
2003 else if( remoteChanges.count( path ) )
2004 {
2005 m_TreeProject->SetItemState(
2006 iter->second,
2007 static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND ) );
2008 continue;
2009 }
2010 else
2011 {
2012 m_TreeProject->SetItemState(
2013 iter->second,
2015 continue;
2016 }
2017 }
2018
2019 git_status_list_free( status_list );
2020 git_index_free( index );
2021}
2022
2023
2024void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
2025{
2026 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
2027
2028 git_repository* repo = m_TreeProject->GetGitRepo();
2029
2030 if( repo == nullptr )
2031 {
2032 wxMessageBox( "The selected directory is not a git project." );
2033 return;
2034 }
2035
2036 git_config* config = nullptr;
2037 git_repository_config( &config, repo );
2038
2039 // Read relevant data from the git config
2040 wxString authorName;
2041 wxString authorEmail;
2042
2043 // Read author name
2044 git_config_entry* name_c = nullptr;
2045 git_config_entry* email_c = nullptr;
2046 int authorNameError = git_config_get_entry( &name_c, config, "user.name" );
2047
2048 if( authorNameError != 0 || name_c == nullptr )
2049 {
2050 authorName = Pgm().GetCommonSettings()->m_Git.authorName;
2051 }
2052 else
2053 {
2054 authorName = name_c->value;
2055 git_config_entry_free( name_c );
2056 }
2057
2058 // Read author email
2059 int authorEmailError = git_config_get_entry( &email_c, config, "user.email" );
2060
2061 if( authorEmailError != 0 || email_c == nullptr )
2062 {
2063 authorEmail = Pgm().GetCommonSettings()->m_Git.authorEmail;
2064 }
2065 else
2066 {
2067 authorEmail = email_c->value;
2068 git_config_entry_free( email_c );
2069 }
2070
2071 // Free the config object
2072 git_config_free( config );
2073
2074 // Collect modified files in the repository
2075 git_status_options status_options = GIT_STATUS_OPTIONS_INIT;
2076 status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
2077 status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
2078
2079 git_status_list* status_list = nullptr;
2080 git_status_list_new( &status_list, repo, &status_options );
2081
2082 std::map<wxString, int> modifiedFiles;
2083
2084 size_t count = git_status_list_entrycount( status_list );
2085
2086 std::set<wxString> selected_files;
2087
2088 for( PROJECT_TREE_ITEM* item : tree_data )
2089 {
2090 if( item->GetType() != TREE_FILE_TYPE::DIRECTORY )
2091 selected_files.emplace( item->GetFileName() );
2092 }
2093
2094 for( size_t i = 0; i < count; ++i )
2095 {
2096 const git_status_entry* entry = git_status_byindex( status_list, i );
2097
2098 // Check if the file is modified (index or workdir changes)
2099 if( entry->status == GIT_STATUS_CURRENT
2100 || ( entry->status & ( GIT_STATUS_CONFLICTED | GIT_STATUS_IGNORED ) ) )
2101 {
2102 continue;
2103 }
2104
2105 wxFileName fn( entry->index_to_workdir->old_file.path );
2106 fn.MakeAbsolute( git_repository_workdir( repo ) );
2107
2108 wxString filePath( entry->index_to_workdir->old_file.path, wxConvUTF8 );
2109
2110 if( aEvent.GetId() == ID_GIT_COMMIT_PROJECT )
2111 {
2112 modifiedFiles.emplace( filePath, entry->status );
2113 }
2114 else if( selected_files.count( fn.GetFullPath() ) )
2115 {
2116 modifiedFiles.emplace( filePath, entry->status );
2117 }
2118 }
2119
2120 git_status_list_free( status_list );
2121
2122 // Create a commit dialog
2123 DIALOG_GIT_COMMIT dlg( this, repo, authorName, authorEmail, modifiedFiles );
2124 auto ret = dlg.ShowModal();
2125
2126 if( ret == wxID_OK )
2127 {
2128 // Commit the changes
2129 git_oid tree_id;
2130 git_tree* tree = nullptr;
2131 git_commit* parent = nullptr;
2132 git_index* index = nullptr;
2133
2134 std::vector<wxString> files = dlg.GetSelectedFiles();
2135
2136 if( dlg.GetCommitMessage().IsEmpty() )
2137 {
2138 wxMessageBox( _( "Discarding commit due to empty commit message." ) );
2139 return;
2140 }
2141
2142 if( files.empty() )
2143 {
2144 wxMessageBox( _( "Discarding commit due to empty file selection." ) );
2145 return;
2146 }
2147
2148 if( git_repository_index( &index, repo ) != 0 )
2149 {
2150 wxMessageBox( _( "Failed to get repository index: %s" ), giterr_last()->message );
2151 return;
2152 }
2153
2154 for( wxString& file :files )
2155 {
2156 if( git_index_add_bypath( index, file.mb_str() ) != 0 )
2157 {
2158 wxMessageBox( _( "Failed to add file to index: %s" ), giterr_last()->message );
2159 git_index_free( index );
2160 return;
2161 }
2162 }
2163
2164 if( git_index_write( index ) != 0 )
2165 {
2166 wxMessageBox( _( "Failed to write index: %s" ), giterr_last()->message );
2167 git_index_free( index );
2168 return;
2169 }
2170
2171 if (git_index_write_tree( &tree_id, index ) != 0)
2172 {
2173 wxMessageBox( _( "Failed to write tree: %s" ), giterr_last()->message );
2174 git_index_free( index );
2175 return;
2176 }
2177
2178 git_index_free( index );
2179
2180 if( git_tree_lookup( &tree, repo, &tree_id ) != 0 )
2181 {
2182 wxMessageBox( _( "Failed to lookup tree: %s" ), giterr_last()->message );
2183 return;
2184 }
2185
2186 git_reference* headRef = nullptr;
2187
2188 if( git_repository_head( &headRef, repo ) != 0 )
2189 {
2190 wxMessageBox( _( "Failed to get HEAD reference: %s" ), giterr_last()->message );
2191 git_index_free( index );
2192 return;
2193 }
2194
2195 if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
2196 {
2197 wxMessageBox( _( "Failed to get commit: %s" ), giterr_last()->message );
2198 git_reference_free( headRef );
2199 git_index_free( index );
2200 return;
2201 }
2202
2203 git_reference_free( headRef );
2204
2205 const wxString& commit_msg = dlg.GetCommitMessage();
2206 const wxString& author_name = dlg.GetAuthorName();
2207 const wxString& author_email = dlg.GetAuthorEmail();
2208
2209 git_signature* author = nullptr;
2210
2211 if( git_signature_now( &author, author_name.mb_str(), author_email.mb_str() ) != 0 )
2212 {
2213 wxMessageBox( _( "Failed to create author signature: %s" ), giterr_last()->message );
2214 return;
2215 }
2216
2217 git_oid oid;
2218 const git_commit* parents[1] = { parent };
2219
2220 if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr, commit_msg.mb_str(), tree,
2221 1, parents ) != 0 )
2222 {
2223 wxMessageBox( _( "Failed to create commit: %s" ), giterr_last()->message );
2224 return;
2225 }
2226
2227 git_signature_free( author );
2228 git_commit_free( parent );
2229 git_tree_free( tree );
2230 }
2231}
2232
2233void PROJECT_TREE_PANE::onGitAddToIndex( wxCommandEvent& aEvent )
2234{
2235
2236}
2237
2238
2239bool PROJECT_TREE_PANE::canFileBeAddedToVCS( const wxString& aFile )
2240{
2241 git_index *index;
2242 size_t entry_pos;
2243
2244 git_repository* repo = m_TreeProject->GetGitRepo();
2245
2246 if( !repo )
2247 return false;
2248
2249 if( git_repository_index( &index, repo ) != 0 )
2250 return false;
2251
2252 // If we successfully find the file in the index, we may not add it to the VCS
2253 if( git_index_find( &entry_pos, index, aFile.mb_str() ) == 0 )
2254 {
2255 git_index_free( index );
2256 return false;
2257 }
2258
2259 git_index_free( index );
2260 return true;
2261}
2262
2263
2264void PROJECT_TREE_PANE::onGitSyncProject( wxCommandEvent& aEvent )
2265{
2266 git_repository* repo = m_TreeProject->GetGitRepo();
2267
2268 if( !repo )
2269 return;
2270
2271 GIT_SYNC_HANDLER handler( repo );
2272 handler.PerformSync();
2273}
2274
2275
2276void PROJECT_TREE_PANE::onGitFetch( wxCommandEvent& aEvent )
2277{
2278 git_repository* repo = m_TreeProject->GetGitRepo();
2279
2280 if( !repo )
2281 return;
2282
2283 GIT_PULL_HANDLER handler( repo );
2284 handler.PerformFetch();
2285}
2286
2287void PROJECT_TREE_PANE::onGitResolveConflict( wxCommandEvent& aEvent )
2288{
2289 git_repository* repo = m_TreeProject->GetGitRepo();
2290
2291 if( !repo )
2292 return;
2293
2294 GIT_RESOLVE_CONFLICT_HANDLER handler( repo );
2295 handler.PerformResolveConflict();
2296}
2297
2298void PROJECT_TREE_PANE::onGitRevertLocal( wxCommandEvent& aEvent )
2299{
2300 git_repository* repo = m_TreeProject->GetGitRepo();
2301
2302 if( !repo )
2303 return;
2304
2305 GIT_REVERT_HANDLER handler( repo );
2306 handler.PerformRevert();
2307}
2308
2309void PROJECT_TREE_PANE::onGitRemoveFromIndex( wxCommandEvent& aEvent )
2310{
2311 git_repository* repo = m_TreeProject->GetGitRepo();
2312
2313 if( !repo )
2314 return;
2315
2316 GIT_REMOVE_FROM_INDEX_HANDLER handler( repo );
2317 handler.PerformRemoveFromIndex();
2318}
2319
2320
2321
const char * name
Definition: DXF_plotter.cpp:57
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
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 GetBareRepoURL() const
Get the Bare Repo U R L object.
wxString GetRepoSSHPath() const
wxString GetUsername() const
wxString GetPassword() const
wxString GetBranchName() const
void SetProgressReporter(std::unique_ptr< WX_PROGRESS_REPORTER > aProgressReporter)
Definition: git_progress.h:40
PullResult PerformPull()
PushResult PerformPush()
The main KiCad project manager frame.
PROJECT_TREE_PANE * m_leftWin
const wxString GetProjectFileName() const
void OnChangeWatchedPaths(wxCommandEvent &aEvent)
Called by sending a event with id = ID_INIT_WATCHED_PATHS rebuild the list of watched paths.
void LoadProject(const wxFileName &aProjectFileName)
std::vector< wxString > GetBranchNames() const
void SetConnType(GIT_CONN_TYPE aConnType)
wxString GetSSHKey() const
void SetSSHKey(const wxString &aSSHKey)
void SetUsername(const wxString &aUsername)
GIT_CONN_TYPE GetConnType() const
wxString GetPassword() const
std::pair< std::set< wxString >, std::set< wxString > > GetDifferentFiles() const
Return a pair of sets of files that differ locally from the remote repository The first set is files ...
bool HasPushAndPullRemote() const
wxString GetUsername() const
bool HasLocalCommits() const
void SetPassword(const wxString &aPassword)
wxString GetErrorString()
static wxString GetDefaultUserProjectsPath()
Gets the default path we point users to create projects.
Definition: paths.cpp:140
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
void onGitFetch(wxCommandEvent &event)
Fetch the latest changes from the git repository.
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
wxDateTime m_lastGitStatusUpdate
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
KICAD_MANAGER_FRAME * m_Parent
void onExpand(wxTreeEvent &Event)
Called on a click on the + or - button of an item with children.
void onIdle(wxIdleEvent &aEvent)
Idle event handler, used process the selected items at a point in time when all other events have bee...
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 onGitCompare(wxCommandEvent &event)
Compare the current project to a different branch in the git repository.
void shutdownFileWatcher()
Shutdown the file watcher.
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.
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.
Definition: project_tree.h:41
git_repository * GetGitRepo() const
Definition: project_tree.h:59
KIGIT_COMMON * GitCommon() const
Definition: project_tree.h:61
void LoadIcons()
void SetGitRepo(git_repository *aRepo)
Definition: project_tree.h:58
virtual PROJECT_LOCAL_SETTINGS & GetLocalSettings() const
Definition: project.h:172
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition: confirm.cpp:360
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:280
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition: confirm.cpp:332
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:305
This file is part of the common library.
#define _(s)
bool RmDirRecursive(const wxString &aFileName, wxString *aErrors)
Removes the directory aDirName and all its contents including subdirectories and their files.
Definition: gestfich.cpp:299
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback)
Call the executable file aEditorName with the parameter aFileName.
Definition: gestfich.cpp:120
bool m_EnableGit
When true, enable git integration.
const std::string SpiceFileExtension
const std::string LegacyPcbFileExtension
const std::string PdfFileExtension
const std::string KiCadSymbolLibFileExtension
const std::string LegacySchematicFileExtension
const std::string FootprintAssignmentFileExtension
const std::string FootprintPlaceFileExtension
const std::string GerberJobFileExtension
const std::string DrillFileExtension
const wxString GerberFileExtensionsRegex
const std::string KiCadFootprintFileExtension
const std::string LegacyProjectFileExtension
const std::string MarkdownFileExtension
const std::string KiCadPcbFileExtension
const std::string SVGFileExtension
const std::string TextFileExtension
const std::string ProjectFileExtension
const std::string NetlistFileExtension
const std::string DesignRulesFileExtension
const std::string HtmlFileExtension
const std::string KiCadSchematicFileExtension
const std::string ReportFileExtension
const std::string DrawingSheetFileExtension
const std::string ArchiveFileExtension
const std::string LegacySymbolLibFileExtension
const wxChar *const tracePathsAndFiles
Flag to enable path and file name debug output.
PROJECT & Prj()
Definition: kicad.cpp:576
#define GIT_BUF_INIT
IDs used in KiCad main frame foe menuitems and tools.
@ ID_PROJECT_OPEN_DIR
Definition: kicad_id.h:62
@ ID_GIT_PUSH
Definition: kicad_id.h:82
@ ID_GIT_REVERT_LOCAL
Definition: kicad_id.h:85
@ ID_PROJECT_SWITCH_TO_OTHER
Definition: kicad_id.h:60
@ ID_PROJECT_NEWDIR
Definition: kicad_id.h:61
@ ID_PROJECT_RENAME
Definition: kicad_id.h:64
@ ID_GIT_REMOVE_VCS
Definition: kicad_id.h:87
@ ID_GIT_COMMIT_FILE
Definition: kicad_id.h:79
@ ID_GIT_PULL
Definition: kicad_id.h:83
@ ID_GIT_COMPARE
Definition: kicad_id.h:86
@ ID_PROJECT_TXTEDIT
Definition: kicad_id.h:59
@ ID_GIT_REMOVE_FROM_INDEX
Definition: kicad_id.h:89
@ ID_GIT_RESOLVE_CONFLICT
Definition: kicad_id.h:84
@ ID_GIT_COMMIT_PROJECT
Definition: kicad_id.h:78
@ ID_GIT_SWITCH_BRANCH
Definition: kicad_id.h:90
@ ID_GIT_ADD_TO_INDEX
Definition: kicad_id.h:88
@ ID_GIT_SYNC_PROJECT
Definition: kicad_id.h:80
@ ID_PROJECT_TREE
Definition: kicad_id.h:58
@ ID_GIT_FETCH
Definition: kicad_id.h:81
@ ID_GIT_INITIALIZE_PROJECT
Definition: kicad_id.h:76
@ ID_PROJECT_DELETE
Definition: kicad_id.h:63
@ ID_LEFT_FRAME
Definition: kicad_id.h:57
bool LaunchExternal(const wxString &aPath)
Launches the given file or folder in the host OS.
Definition: launch_ext.cpp:25
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 ...
wxMenuItem * AddMenuItem(wxMenu *aMenu, int aId, const wxString &aText, const wxBitmap &aImage, wxItemKind aType=wxITEM_NORMAL)
Create and insert a menu item with an icon into aMenu.
Definition: ui_common.cpp:369
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition: kicad_algo.h:100
#define NAMELESS_PROJECT
default name for nameless projects
Definition: project.h:42
std::vector< wxString > getProjects(const wxDir &dir)
static git_repository * get_git_repository_for_file(const char *filename)
static const wxChar * s_allowedExtensionsToList[]
static int git_create_branch(git_repository *aRepo, wxString &aBranchName)
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:119
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:391
wxLogTrace helper definitions.
TREE_FILE_TYPE
Definition of file extensions used in Kicad.