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