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