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