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