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();
747
748 bool can_switch_to_project = true;
749 bool can_create_new_directory = true;
750 bool can_open_this_directory = true;
751 bool can_edit = true;
752 bool can_rename = true;
753 bool can_delete = true;
754 bool run_jobs = false;
755
756 bool vcs_has_repo = m_TreeProject->GetGitRepo() != nullptr;
757 bool vcs_can_commit = hasChangedFiles();
758 bool vcs_can_init = !vcs_has_repo;
759 bool vcs_can_remove = vcs_has_repo;
760 bool vcs_can_fetch = vcs_has_repo && m_TreeProject->GitCommon()->HasPushAndPullRemote();
761 bool vcs_can_push = vcs_can_fetch && m_TreeProject->GitCommon()->HasLocalCommits();
762 bool vcs_can_pull = vcs_can_fetch;
763 bool vcs_can_switch = vcs_has_repo;
764 bool vcs_menu = ADVANCED_CFG::GetCfg().m_EnableGit;
765
766 // Check if the libgit2 library has been successfully initialized
767#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
768 int major, minor, rev;
769 bool libgit_init = ( git_libgit2_version( &major, &minor, &rev ) == GIT_OK );
770#else
771 //Work around libgit2 API change for supporting older platforms
772 bool libgit_init = true;
773#endif
774
775 vcs_menu &= libgit_init;
776
777 if( selection.size() == 0 )
778 return;
779
780 // Remove things that don't make sense for multiple selections
781 if( selection.size() != 1 )
782 {
783 can_switch_to_project = false;
784 can_create_new_directory = false;
785 can_rename = false;
786 }
787
788 for( PROJECT_TREE_ITEM* item : selection )
789 {
790 // Check for empty project
791 if( !item )
792 {
793 can_switch_to_project = false;
794 can_edit = false;
795 can_rename = false;
796 continue;
797 }
798
799 can_delete = item->CanDelete();
800 can_rename = item->CanRename();
801
802 switch( item->GetType() )
803 {
804 case TREE_FILE_TYPE::JSON_PROJECT:
805 case TREE_FILE_TYPE::LEGACY_PROJECT:
806 can_rename = false;
807
808 if( item->GetId() == m_TreeProject->GetRootItem() )
809 {
810 can_switch_to_project = false;
811 }
812 else
813 {
814 can_create_new_directory = false;
815 can_open_this_directory = false;
816 }
817 break;
818
819 case TREE_FILE_TYPE::DIRECTORY:
820 can_switch_to_project = false;
821 can_edit = false;
822 break;
823
824 case TREE_FILE_TYPE::ZIP_ARCHIVE:
825 case TREE_FILE_TYPE::PDF:
826 can_edit = false;
827 can_switch_to_project = false;
828 can_create_new_directory = false;
829 can_open_this_directory = false;
830 break;
831
832 case TREE_FILE_TYPE::JOBSET_FILE:
833 run_jobs = true;
834 can_edit = false;
836
837 case TREE_FILE_TYPE::SEXPR_SCHEMATIC:
838 case TREE_FILE_TYPE::SEXPR_PCB:
840
841 default:
842 can_switch_to_project = false;
843 can_create_new_directory = false;
844 can_open_this_directory = false;
845
846 break;
847 }
848 }
849
850 wxMenu popup_menu;
851 wxString text;
852 wxString help_text;
853
854 if( can_switch_to_project )
855 {
856 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_SWITCH_TO_OTHER, _( "Switch to this Project" ),
857 _( "Close all editors, and switch to the selected project" ),
858 KiBitmap( BITMAPS::open_project ) );
859 popup_menu.AppendSeparator();
860 }
861
862 if( can_create_new_directory )
863 {
864 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_NEWDIR, _( "New Directory..." ),
865 _( "Create a New Directory" ), KiBitmap( BITMAPS::directory ) );
866 }
867
868 if( can_open_this_directory )
869 {
870 if( selection.size() == 1 )
871 {
872#ifdef __APPLE__
873 text = _( "Reveal in Finder" );
874 help_text = _( "Reveals the directory in a Finder window" );
875#else
876 text = _( "Open Directory in File Explorer" );
877 help_text = _( "Opens the directory in the default system file manager" );
878#endif
879 }
880 else
881 {
882#ifdef __APPLE__
883 text = _( "Reveal in Finder" );
884 help_text = _( "Reveals the directories in a Finder window" );
885#else
886 text = _( "Open Directories in File Explorer" );
887 help_text = _( "Opens the directories in the default system file manager" );
888#endif
889 }
890
891 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_OPEN_DIR, text, help_text,
892 KiBitmap( BITMAPS::directory_browser ) );
893 }
894
895 if( can_edit )
896 {
897 if( selection.size() == 1 )
898 help_text = _( "Open the file in a Text Editor" );
899 else
900 help_text = _( "Open files in a Text Editor" );
901
902 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_TXTEDIT, _( "Edit in a Text Editor" ), help_text,
903 KiBitmap( BITMAPS::editor ) );
904 }
905
906 if( run_jobs && selection.size() == 1 )
907 {
908 KIUI::AddMenuItem( &popup_menu, ID_JOBS_RUN, _( "Run Jobs" ), help_text,
909 KiBitmap( BITMAPS::exchange ) );
910 }
911
912 if( can_rename )
913 {
914 if( selection.size() == 1 )
915 {
916 text = _( "Rename File..." );
917 help_text = _( "Rename file" );
918 }
919 else
920 {
921 text = _( "Rename Files..." );
922 help_text = _( "Rename files" );
923 }
924
925 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_RENAME, text, help_text,
926 KiBitmap( BITMAPS::right ) );
927 }
928
929 if( can_delete )
930 {
931 if( selection.size() == 1 )
932 help_text = _( "Delete the file and its content" );
933 else
934 help_text = _( "Delete the files and their contents" );
935
936 if( can_switch_to_project
937 || can_create_new_directory
938 || can_open_this_directory
939 || can_edit
940 || can_rename )
941 {
942 popup_menu.AppendSeparator();
943 }
944
945#ifdef __WINDOWS__
946 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Delete" ), help_text,
947 KiBitmap( BITMAPS::trash ) );
948#else
949 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Move to Trash" ), help_text,
950 KiBitmap( BITMAPS::trash ) );
951#endif
952 }
953
954 if( vcs_menu )
955 {
956 wxMenu* vcs_submenu = new wxMenu();
957 wxMenu* branch_submenu = new wxMenu();
958 wxMenuItem* vcs_menuitem = nullptr;
959
960 vcs_menuitem = vcs_submenu->Append( ID_GIT_INITIALIZE_PROJECT,
961 _( "Add Project to Version Control..." ),
962 _( "Initialize a new repository" ) );
963 vcs_menuitem->Enable( vcs_can_init );
964
965
966 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_PROJECT, _( "Commit Project..." ),
967 _( "Commit changes to the local repository" ) );
968 vcs_menuitem->Enable( vcs_can_commit );
969
970 vcs_menuitem = vcs_submenu->Append( ID_GIT_PUSH, _( "Push" ),
971 _( "Push committed local changes to remote repository" ) );
972 vcs_menuitem->Enable( vcs_can_push );
973
974 vcs_menuitem = vcs_submenu->Append( ID_GIT_PULL, _( "Pull" ),
975 _( "Pull changes from remote repository into local" ) );
976 vcs_menuitem->Enable( vcs_can_pull );
977
978 vcs_submenu->AppendSeparator();
979
980 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_FILE, _( "Commit File..." ),
981 _( "Commit changes to the local repository" ) );
982 vcs_menuitem->Enable( vcs_can_commit );
983
984 vcs_submenu->AppendSeparator();
985
986 // vcs_menuitem = vcs_submenu->Append( ID_GIT_COMPARE, _( "Diff" ),
987 // _( "Show changes between the repository and working tree" ) );
988 // vcs_menuitem->Enable( vcs_can_diff );
989
990 std::vector<wxString> branchNames = m_TreeProject->GitCommon()->GetBranchNames();
991
992 // Skip the first one (that is the current branch)
993 for( size_t ii = 1; ii < branchNames.size() && ii < 6; ++ii )
994 {
995 wxString msg = _( "Switch to branch " ) + branchNames[ii];
996 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH + ii, branchNames[ii], msg );
997 vcs_menuitem->Enable( vcs_can_switch );
998 }
999
1000 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Other..." ),
1001 _( "Switch to a different branch" ) );
1002 vcs_menuitem->Enable( vcs_can_switch );
1003
1004 vcs_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Switch to Branch" ), branch_submenu );
1005
1006 vcs_submenu->AppendSeparator();
1007
1008 vcs_menuitem = vcs_submenu->Append( ID_GIT_REMOVE_VCS, _( "Remove Version Control" ),
1009 _( "Delete all version control files from the project directory." ) );
1010 vcs_menuitem->Enable( vcs_can_remove );
1011
1012 popup_menu.AppendSeparator();
1013 popup_menu.AppendSubMenu( vcs_submenu, _( "Version Control" ) );
1014 }
1015
1016 if( popup_menu.GetMenuItemCount() > 0 )
1017 PopupMenu( &popup_menu );
1018}
1019
1020
1022{
1023 wxString editorname = Pgm().GetTextEditor();
1024
1025 if( editorname.IsEmpty() )
1026 {
1027 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
1028 return;
1029 }
1030
1031 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1032
1033 for( PROJECT_TREE_ITEM* item_data : tree_data )
1034 {
1035 wxString fullFileName = item_data->GetFileName();
1036
1037 if( !fullFileName.IsEmpty() )
1038 {
1039 ExecuteFile( editorname, fullFileName.wc_str(), nullptr, false );
1040 }
1041 }
1042}
1043
1044
1045void PROJECT_TREE_PANE::onDeleteFile( wxCommandEvent& event )
1046{
1047 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1048
1049 for( PROJECT_TREE_ITEM* item_data : tree_data )
1050 item_data->Delete();
1051}
1052
1053
1054void PROJECT_TREE_PANE::onRenameFile( wxCommandEvent& event )
1055{
1056 wxTreeItemId curr_item = m_TreeProject->GetFocusedItem();
1057 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1058
1059 // XXX: Unnecessary?
1060 if( tree_data.size() != 1 )
1061 return;
1062
1063 wxString buffer = m_TreeProject->GetItemText( curr_item );
1064 wxString msg = wxString::Format( _( "Change filename: '%s'" ),
1065 tree_data[0]->GetFileName() );
1066 wxTextEntryDialog dlg( wxGetTopLevelParent( this ), msg, _( "Change filename" ), buffer );
1067
1068 if( dlg.ShowModal() != wxID_OK )
1069 return; // canceled by user
1070
1071 buffer = dlg.GetValue();
1072 buffer.Trim( true );
1073 buffer.Trim( false );
1074
1075 if( buffer.IsEmpty() )
1076 return; // empty file name not allowed
1077
1078 tree_data[0]->Rename( buffer, true );
1079 m_isRenaming = true;
1080}
1081
1082
1083void PROJECT_TREE_PANE::onSelect( wxTreeEvent& Event )
1084{
1085 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1086
1087 if( tree_data.size() != 1 )
1088 return;
1089
1090 // Bookmark the selected item but don't try and activate it until later. If we do it now,
1091 // there will be more events at least on Windows in this frame that will steal focus from
1092 // any newly launched windows
1093 m_selectedItem = tree_data[0];
1094}
1095
1096
1097void PROJECT_TREE_PANE::onIdle( wxIdleEvent& aEvent )
1098{
1099 // Idle executes once all other events finished processing. This makes it ideal to launch
1100 // a new window without starting Focus wars.
1101 if( m_watcherNeedReset )
1102 {
1103 m_selectedItem = nullptr;
1105 }
1106
1107 if( m_selectedItem != nullptr )
1108 {
1109 // Activate launches a window which may run the event loop on top of us and cause OnIdle
1110 // to get called again, so be sure to block off the activation condition first.
1112 m_selectedItem = nullptr;
1113
1114 item->Activate( this );
1115 }
1116
1117 // Inside this routine, we rate limit to once per 2 seconds
1119}
1120
1121
1122void PROJECT_TREE_PANE::onExpand( wxTreeEvent& Event )
1123{
1124 wxTreeItemId itemId = Event.GetItem();
1125 PROJECT_TREE_ITEM* tree_data = GetItemIdData( itemId );
1126
1127 if( !tree_data )
1128 return;
1129
1130 if( tree_data->GetType() != TREE_FILE_TYPE::DIRECTORY )
1131 return;
1132
1133 // explore list of non populated subdirs, and populate them
1134 wxTreeItemIdValue cookie;
1135 wxTreeItemId kid = m_TreeProject->GetFirstChild( itemId, cookie );
1136
1137#ifndef __WINDOWS__
1138 bool subdir_populated = false;
1139#endif
1140
1141 for( ; kid.IsOk(); kid = m_TreeProject->GetNextChild( itemId, cookie ) )
1142 {
1143 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1144
1145 if( !itemData || itemData->GetType() != TREE_FILE_TYPE::DIRECTORY )
1146 continue;
1147
1148 if( itemData->IsPopulated() )
1149 continue;
1150
1151 wxString fileName = itemData->GetFileName();
1152 wxDir dir( fileName );
1153
1154 if( dir.IsOpened() )
1155 {
1156 std::vector<wxString> projects = getProjects( dir );
1157 wxString dir_filename;
1158 bool haveFile = dir.GetFirst( &dir_filename );
1159
1160 while( haveFile )
1161 {
1162 // Add name to tree item, but do not recurse in subdirs:
1163 wxString name = fileName + wxFileName::GetPathSeparator() + dir_filename;
1164 addItemToProjectTree( name, kid, &projects, false );
1165
1166 haveFile = dir.GetNext( &dir_filename );
1167 }
1168
1169 itemData->SetPopulated( true ); // set state to populated
1170
1171#ifndef __WINDOWS__
1172 subdir_populated = true;
1173#endif
1174 }
1175
1176 // Sort filenames by alphabetic order
1177 m_TreeProject->SortChildren( kid );
1178 }
1179
1180#ifndef __WINDOWS__
1181 if( subdir_populated )
1182 m_watcherNeedReset = true;
1183#endif
1184}
1185
1186
1187std::vector<PROJECT_TREE_ITEM*> PROJECT_TREE_PANE::GetSelectedData()
1188{
1189 wxArrayTreeItemIds selection;
1190 std::vector<PROJECT_TREE_ITEM*> data;
1191
1192 m_TreeProject->GetSelections( selection );
1193
1194 for( auto it = selection.begin(); it != selection.end(); it++ )
1195 {
1196 PROJECT_TREE_ITEM* item = GetItemIdData( *it );
1197
1198 if( !item )
1199 {
1200 wxLogTrace( traceGit, wxS( "Null tree item returned for selection, dynamic_cast "
1201 "failed?" ) );
1202 continue;
1203 }
1204
1205 data.push_back( item );
1206 }
1207
1208 return data;
1209}
1210
1211
1213{
1214 return dynamic_cast<PROJECT_TREE_ITEM*>( m_TreeProject->GetItemData( aId ) );
1215}
1216
1217
1218wxTreeItemId PROJECT_TREE_PANE::findSubdirTreeItem( const wxString& aSubDir )
1219{
1220 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1221
1222 // If the subdir is the current working directory, return m_root
1223 // in main list:
1224 if( prj_dir == aSubDir )
1225 return m_root;
1226
1227 // The subdir is in the main tree or in a subdir: Locate it
1228 wxTreeItemIdValue cookie;
1229 wxTreeItemId root_id = m_root;
1230 std::stack<wxTreeItemId> subdirs_id;
1231
1232 wxTreeItemId child = m_TreeProject->GetFirstChild( root_id, cookie );
1233
1234 while( true )
1235 {
1236 if( ! child.IsOk() )
1237 {
1238 if( subdirs_id.empty() ) // all items were explored
1239 {
1240 root_id = child; // Not found: return an invalid wxTreeItemId
1241 break;
1242 }
1243 else
1244 {
1245 root_id = subdirs_id.top();
1246 subdirs_id.pop();
1247 child = m_TreeProject->GetFirstChild( root_id, cookie );
1248
1249 if( !child.IsOk() )
1250 continue;
1251 }
1252 }
1253
1254 PROJECT_TREE_ITEM* itemData = GetItemIdData( child );
1255
1256 if( itemData && ( itemData->GetType() == TREE_FILE_TYPE::DIRECTORY ) )
1257 {
1258 if( itemData->GetFileName() == aSubDir ) // Found!
1259 {
1260 root_id = child;
1261 break;
1262 }
1263
1264 // child is a subdir, push in list to explore it later
1265 if( itemData->IsPopulated() )
1266 subdirs_id.push( child );
1267 }
1268
1269 child = m_TreeProject->GetNextChild( root_id, cookie );
1270 }
1271
1272 return root_id;
1273}
1274
1275
1276void PROJECT_TREE_PANE::onFileSystemEvent( wxFileSystemWatcherEvent& event )
1277{
1278 // No need to process events when we're shutting down
1279 if( !m_watcher )
1280 return;
1281
1282 const wxFileName& pathModified = event.GetPath();
1283 wxString subdir = pathModified.GetPath();
1284 wxString fn = pathModified.GetFullPath();
1285
1286 // Adjust directories to look like a file item (path and name).
1287 if( pathModified.GetFullName().IsEmpty() )
1288 {
1289 subdir = subdir.BeforeLast( '/' );
1290 fn = fn.BeforeLast( '/' );
1291 }
1292
1293 switch( event.GetChangeType() )
1294 {
1295 case wxFSW_EVENT_DELETE:
1296 case wxFSW_EVENT_CREATE:
1297 case wxFSW_EVENT_RENAME:
1299 break;
1300
1301 case wxFSW_EVENT_MODIFY:
1304 case wxFSW_EVENT_ACCESS:
1305 default:
1306 return;
1307 }
1308
1309 wxTreeItemId root_id = findSubdirTreeItem( subdir );
1310
1311 if( !root_id.IsOk() )
1312 return;
1313
1314 wxTreeItemIdValue cookie; // dummy variable needed by GetFirstChild()
1315 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1316
1317 switch( event.GetChangeType() )
1318 {
1319 case wxFSW_EVENT_CREATE:
1320 {
1321 wxTreeItemId newitem = addItemToProjectTree( fn, root_id, nullptr, true );
1322
1323 // If we are in the process of renaming a file, select the new one
1324 // This is needed for MSW and OSX, since we don't get RENAME events from them, just a
1325 // pair of DELETE and CREATE events.
1326 if( m_isRenaming && newitem.IsOk() )
1327 {
1328 m_TreeProject->SelectItem( newitem );
1329 m_isRenaming = false;
1330 }
1331 }
1332 break;
1333
1334 case wxFSW_EVENT_DELETE:
1335 while( kid.IsOk() )
1336 {
1337 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1338
1339 if( itemData && itemData->GetFileName() == fn )
1340 {
1341 m_TreeProject->Delete( kid );
1342 return;
1343 }
1344 kid = m_TreeProject->GetNextChild( root_id, cookie );
1345 }
1346 break;
1347
1348 case wxFSW_EVENT_RENAME :
1349 {
1350 const wxFileName& newpath = event.GetNewPath();
1351 wxString newdir = newpath.GetPath();
1352 wxString newfn = newpath.GetFullPath();
1353
1354 while( kid.IsOk() )
1355 {
1356 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1357
1358 if( itemData && itemData->GetFileName() == fn )
1359 {
1360 m_TreeProject->Delete( kid );
1361 break;
1362 }
1363
1364 kid = m_TreeProject->GetNextChild( root_id, cookie );
1365 }
1366
1367 // Add the new item only if it is not the current project file (root item).
1368 // Remember: this code is called by a wxFileSystemWatcherEvent event, and not always
1369 // called after an actual file rename, and the cleanup code does not explore the
1370 // root item, because it cannot be renamed by the user. Also, ensure the new file
1371 // actually exists on the file system before it is readded. On Linux, moving a file
1372 // to the trash can cause the same path to be returned in both the old and new paths
1373 // of the event, even though the file isn't there anymore.
1374 PROJECT_TREE_ITEM* rootData = GetItemIdData( root_id );
1375
1376 if( rootData && newpath.Exists() && ( newfn != rootData->GetFileName() ) )
1377 {
1378 wxTreeItemId newroot_id = findSubdirTreeItem( newdir );
1379 wxTreeItemId newitem = addItemToProjectTree( newfn, newroot_id, nullptr, true );
1380
1381 // If the item exists, select it
1382 if( newitem.IsOk() )
1383 m_TreeProject->SelectItem( newitem );
1384 }
1385
1386 m_isRenaming = false;
1387 }
1388 break;
1389 }
1390
1391 // Sort filenames by alphabetic order
1392 m_TreeProject->SortChildren( root_id );
1393}
1394
1395
1397{
1398 m_watcherNeedReset = false;
1399
1400 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1401
1402#if defined( _WIN32 )
1403 KISTATUSBAR* statusBar = static_cast<KISTATUSBAR*>( m_Parent->GetStatusBar() );
1404
1405 if( KIPLATFORM::ENV::IsNetworkPath( prj_dir ) )
1406 {
1407 // Due to a combination of a bug in SAMBA sending bad change event IDs and wxWidgets
1408 // choosing to fault on an invalid event ID instead of sanely ignoring them we need to
1409 // avoid spawning a filewatcher. Unfortunately this punishes corporate environments with
1410 // Windows Server shares :/
1411 m_Parent->m_FileWatcherInfo = _( "Network path: not monitoring folder changes" );
1413 return;
1414 }
1415 else
1416 {
1417 m_Parent->m_FileWatcherInfo = _( "Local path: monitoring folder changes" );
1419 }
1420#endif
1421
1422 // Prepare file watcher:
1423 if( m_watcher )
1424 {
1425 m_watcher->RemoveAll();
1426 }
1427 else
1428 {
1430 m_watcher->SetOwner( this );
1431 }
1432
1433 // We can see wxString under a debugger, not a wxFileName
1434 wxFileName fn;
1435 fn.AssignDir( prj_dir );
1436 fn.DontFollowLink();
1437
1438 // Add directories which should be monitored.
1439 // under windows, we add the curr dir and all subdirs
1440 // under unix, we add only the curr dir and the populated subdirs
1441 // see http://docs.wxwidgets.org/trunk/classwx_file_system_watcher.htm
1442 // under unix, the file watcher needs more work to be efficient
1443 // moreover, under wxWidgets 2.9.4, AddTree does not work properly.
1444 {
1445 wxLogNull logNo; // avoid log messages
1446#ifdef __WINDOWS__
1447 if( ! m_watcher->AddTree( fn ) )
1448 {
1449 wxLogTrace( tracePathsAndFiles, "%s: failed to add '%s'\n", __func__,
1450 TO_UTF8( fn.GetFullPath() ) );
1451 return;
1452 }
1453 }
1454#else
1455 if( !m_watcher->Add( fn ) )
1456 {
1457 wxLogTrace( tracePathsAndFiles, "%s: failed to add '%s'\n", __func__,
1458 TO_UTF8( fn.GetFullPath() ) );
1459 return;
1460 }
1461 }
1462
1463 if( m_TreeProject->IsEmpty() )
1464 return;
1465
1466 // Add subdirs
1467 wxTreeItemIdValue cookie;
1468 wxTreeItemId root_id = m_root;
1469
1470 std::stack < wxTreeItemId > subdirs_id;
1471
1472 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1473 int total_watch_count = 0;
1474
1475 while( total_watch_count < ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
1476 {
1477 if( !kid.IsOk() )
1478 {
1479 if( subdirs_id.empty() ) // all items were explored
1480 {
1481 break;
1482 }
1483 else
1484 {
1485 root_id = subdirs_id.top();
1486 subdirs_id.pop();
1487 kid = m_TreeProject->GetFirstChild( root_id, cookie );
1488
1489 if( !kid.IsOk() )
1490 continue;
1491 }
1492 }
1493
1494 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1495
1496 if( itemData && itemData->GetType() == TREE_FILE_TYPE::DIRECTORY )
1497 {
1498 // we can see wxString under a debugger, not a wxFileName
1499 const wxString& path = itemData->GetFileName();
1500
1501 wxLogTrace( tracePathsAndFiles, "%s: add '%s'\n", __func__, TO_UTF8( path ) );
1502
1503 if( wxFileName::IsDirReadable( path ) ) // linux whines about watching protected dir
1504 {
1505 {
1506 // Silence OS errors that come from the watcher
1507 wxLogNull silence;
1508 fn.AssignDir( path );
1509 m_watcher->Add( fn );
1510 total_watch_count++;
1511 }
1512
1513 // if kid is a subdir, push in list to explore it later
1514 if( itemData->IsPopulated() && m_TreeProject->GetChildrenCount( kid ) )
1515 subdirs_id.push( kid );
1516 }
1517 }
1518
1519 kid = m_TreeProject->GetNextChild( root_id, cookie );
1520 }
1521
1522 if( total_watch_count >= ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
1523 wxLogTrace( tracePathsAndFiles, "%s: too many directories to watch\n", __func__ );
1524#endif
1525
1526#if defined(DEBUG) && 1
1527 wxArrayString paths;
1528 m_watcher->GetWatchedPaths( &paths );
1529 wxLogTrace( tracePathsAndFiles, "%s: watched paths:", __func__ );
1530
1531 for( unsigned ii = 0; ii < paths.GetCount(); ii++ )
1532 wxLogTrace( tracePathsAndFiles, " %s\n", TO_UTF8( paths[ii] ) );
1533#endif
1534 }
1535
1536
1538{
1539 // Make sure we don't try to inspect the tree after we've deleted its items.
1541
1542 m_TreeProject->DeleteAllItems();
1543
1544 // Remove the git repository when the project is unloaded
1545 if( m_TreeProject->GetGitRepo() )
1546 {
1547 git_repository_free( m_TreeProject->GetGitRepo() );
1548 m_TreeProject->SetGitRepo( nullptr );
1549 }
1550}
1551
1552
1553void PROJECT_TREE_PANE::onThemeChanged( wxSysColourChangedEvent &aEvent )
1554{
1557 m_TreeProject->Refresh();
1558
1559 aEvent.Skip();
1560}
1561
1562
1563void PROJECT_TREE_PANE::onPaint( wxPaintEvent& event )
1564{
1565 wxRect rect( wxPoint( 0, 0 ), GetClientSize() );
1566 wxPaintDC dc( this );
1567
1568 dc.SetBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_FRAMEBK ) );
1569 dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_ACTIVEBORDER ), 1 ) );
1570
1571 dc.DrawLine( rect.GetLeft(), rect.GetTop(), rect.GetLeft(), rect.GetBottom() );
1572 dc.DrawLine( rect.GetRight(), rect.GetTop(), rect.GetRight(), rect.GetBottom() );
1573}
1574
1575
1576void KICAD_MANAGER_FRAME::OnChangeWatchedPaths( wxCommandEvent& aEvent )
1577{
1579}
1580
1581
1582void PROJECT_TREE_PANE::onGitInitializeProject( wxCommandEvent& aEvent )
1583{
1584 PROJECT_TREE_ITEM* tree_data = GetItemIdData( m_TreeProject->GetRootItem() );
1585
1586 wxString dir = tree_data->GetDir();
1587
1588 if( dir.empty() )
1589 {
1590 wxLogError( "Failed to initialize git project: project directory is empty." );
1591 return;
1592 }
1593
1594 // Check if the directory is already a git repository
1595 git_repository* repo = nullptr;
1596 int error = git_repository_open(&repo, dir.mb_str());
1597
1598 if( error == 0 )
1599 {
1600 // Directory is already a git repository
1601 wxWindow* topLevelParent = wxGetTopLevelParent( this );
1602
1603 DisplayInfoMessage( topLevelParent,
1604 _( "The selected directory is already a git project." ) );
1605 git_repository_free( repo );
1606 return;
1607 }
1608
1609 DIALOG_GIT_REPOSITORY dlg( wxGetTopLevelParent( this ), nullptr );
1610
1611 dlg.SetTitle( _( "Set default remote" ) );
1612
1613 if( dlg.ShowModal() != wxID_OK )
1614 return;
1615
1616 // Directory is not a git repository
1617 error = git_repository_init( &repo, dir.mb_str(), 0 );
1618
1619 if( error != 0 )
1620 {
1621 git_repository_free( repo );
1622
1623 if( m_gitLastError != git_error_last()->klass )
1624 {
1625 m_gitLastError = git_error_last()->klass;
1626 DisplayErrorMessage( m_parent, _( "Failed to initialize git project." ),
1627 git_error_last()->message );
1628 }
1629
1630 return;
1631 }
1632 else
1633 {
1634 m_TreeProject->SetGitRepo( repo );
1635 m_gitLastError = GIT_ERROR_NONE;
1636 }
1637
1638 //Set up the git remote
1639
1644
1645 git_remote* remote = nullptr;
1646 wxString fullURL;
1647
1649 {
1650 fullURL = dlg.GetUsername() + "@" + dlg.GetRepoURL();
1651 }
1653 {
1654 fullURL = dlg.GetRepoURL().StartsWith( "https" ) ? "https://" : "http://";
1655
1656 if( !dlg.GetUsername().empty() )
1657 {
1658 fullURL.append( dlg.GetUsername() );
1659
1660 if( !dlg.GetPassword().empty() )
1661 {
1662 fullURL.append( wxS( ":" ) );
1663 fullURL.append( dlg.GetPassword() );
1664 }
1665
1666 fullURL.append( wxS( "@" ) );
1667 }
1668
1669 fullURL.append( dlg.GetBareRepoURL() );
1670 }
1671 else
1672 {
1673 fullURL = dlg.GetRepoURL();
1674 }
1675
1676
1677 error = git_remote_create_with_fetchspec( &remote, repo, "origin",
1678 fullURL.ToStdString().c_str(),
1679 "+refs/heads/*:refs/remotes/origin/*" );
1680
1681 if( error != GIT_OK )
1682 {
1683 if( m_gitLastError != git_error_last()->klass )
1684 {
1685 m_gitLastError = git_error_last()->klass;
1686 DisplayErrorMessage( m_parent, _( "Failed to set default remote." ),
1687 git_error_last()->message );
1688 }
1689
1690 return;
1691 }
1692
1693 m_gitLastError = GIT_ERROR_NONE;
1694
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
1730
1731 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this,
1732 _( "Fetching Remote" ),
1733 1 ) );
1734
1735 if( handler.PerformPull() < PullResult::Success )
1736 {
1737 wxString errorMessage = handler.GetErrorString();
1738
1739 DisplayErrorMessage( m_parent, _( "Failed to pull project" ), errorMessage );
1740 }
1741}
1742
1743
1744void PROJECT_TREE_PANE::onGitPushProject( wxCommandEvent& aEvent )
1745{
1746 git_repository* repo = m_TreeProject->GetGitRepo();
1747
1748 if( !repo )
1749 return;
1750
1752
1753 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this,
1754 _( "Fetching Remote" ),
1755 1 ) );
1756
1757 if( handler.PerformPush() != PushResult::Success )
1758 {
1759 wxString errorMessage = handler.GetErrorString();
1760
1761 DisplayErrorMessage( m_parent, _( "Failed to push project" ), errorMessage );
1762 }
1763}
1764
1765
1766static int git_create_branch( git_repository* aRepo, wxString& aBranchName )
1767{
1768 git_oid head_oid;
1769
1770 if( int error = git_reference_name_to_id( &head_oid, aRepo, "HEAD" ) != 0 )
1771 {
1772 wxLogError( "Failed to lookup HEAD reference" );
1773 return error;
1774 }
1775
1776 // Lookup the current commit object
1777 git_commit* commit = nullptr;
1778 if( int error = git_commit_lookup( &commit, aRepo, &head_oid ) != GIT_OK )
1779 {
1780 wxLogError( "Failed to lookup commit" );
1781 return error;
1782 }
1783
1784 git_reference* branchRef = nullptr;
1785
1786 if( git_branch_create( &branchRef, aRepo, aBranchName.mb_str(), commit, 0 ) != 0 )
1787 {
1788 wxLogError( "Failed to create branch" );
1789 git_commit_free( commit );
1790 return -1;
1791 }
1792
1793 git_commit_free( commit );
1794 git_reference_free( branchRef );
1795
1796 return 0;
1797}
1798
1799
1800void PROJECT_TREE_PANE::onGitSwitchBranch( wxCommandEvent& aEvent )
1801{
1802 git_repository* repo = m_TreeProject->GetGitRepo();
1803
1804 if( !repo )
1805 return;
1806
1807 wxString branchName;
1808
1809 if( aEvent.GetId() == ID_GIT_SWITCH_BRANCH )
1810 {
1811 DIALOG_GIT_SWITCH dlg( wxGetTopLevelParent( this ), repo );
1812
1813 int retval = dlg.ShowModal();
1814 branchName = dlg.GetBranchName();
1815
1816 if( retval == wxID_ADD )
1817 git_create_branch( repo, branchName );
1818 else if( retval != wxID_OK )
1819 return;
1820 }
1821 else
1822 {
1823 std::vector<wxString> branches = m_TreeProject->GitCommon()->GetBranchNames();
1824 int branchIndex = aEvent.GetId() - ID_GIT_SWITCH_BRANCH;
1825
1826 if( branchIndex < 0 || static_cast<size_t>( branchIndex ) >= branches.size() )
1827 return;
1828
1829 branchName = branches[branchIndex];
1830 }
1831
1832 // Retrieve the reference to the existing branch using libgit2
1833 git_reference* branchRef = nullptr;
1834
1835 if( git_reference_lookup( &branchRef, repo, branchName.mb_str() ) != GIT_OK &&
1836 git_reference_dwim( &branchRef, repo, branchName.mb_str() ) != GIT_OK )
1837 {
1838 wxString errorMessage = wxString::Format( _( "Failed to lookup branch '%s': %s" ),
1839 branchName, giterr_last()->message );
1840 DisplayError( m_parent, errorMessage );
1841 return;
1842 }
1843
1844 const char* branchRefName = git_reference_name( branchRef );
1845
1846 git_object* branchObj = nullptr;
1847
1848 if( git_revparse_single( &branchObj, repo, branchName.mb_str() ) != 0 )
1849 {
1850 wxString errorMessage =
1851 wxString::Format( _( "Failed to find branch head for '%s'" ), branchName );
1852 DisplayError( m_parent, errorMessage );
1853 git_reference_free( branchRef );
1854 return;
1855 }
1856
1857
1858 // Switch to the branch
1859 if( git_checkout_tree( repo, branchObj, nullptr ) != 0 )
1860 {
1861 wxString errorMessage =
1862 wxString::Format( _( "Failed to switch to branch '%s'" ), branchName );
1863 DisplayError( m_parent, errorMessage );
1864 git_reference_free( branchRef );
1865 git_object_free( branchObj );
1866 return;
1867 }
1868
1869 // Update the HEAD reference
1870 if( git_repository_set_head( repo, branchRefName ) != 0 )
1871 {
1872 wxString errorMessage = wxString::Format(
1873 _( "Failed to update HEAD reference for branch '%s'" ), branchName );
1874 DisplayError( m_parent, errorMessage );
1875 git_reference_free( branchRef );
1876 git_object_free( branchObj );
1877 return;
1878 }
1879
1880 // Free resources
1881 git_reference_free( branchRef );
1882 git_object_free( branchObj );
1883}
1884
1885
1886void PROJECT_TREE_PANE::onGitRemoveVCS( wxCommandEvent& aEvent )
1887{
1888 git_repository* repo = m_TreeProject->GetGitRepo();
1889
1890 if( !repo
1891 || !IsOK( wxGetTopLevelParent( this ),
1892 _( "Are you sure you want to remove git tracking from this project?" ) ) )
1893 {
1894 return;
1895 }
1896
1897 // Remove the VCS (git) from the project directory
1898 git_repository_free( repo );
1899 m_TreeProject->SetGitRepo( nullptr );
1900
1901 // Remove the .git directory
1902 wxFileName fn( m_Parent->GetProjectFileName() );
1903 fn.AppendDir( ".git" );
1904
1905 wxString errors;
1906
1907 if( !RmDirRecursive( fn.GetPath(), &errors ) )
1908 {
1909 DisplayErrorMessage( m_parent, _( "Failed to remove git directory" ), errors );
1910 }
1911
1912 // Clear all item states
1913 std::stack<wxTreeItemId> items;
1914 items.push( m_TreeProject->GetRootItem() );
1915
1916 while( !items.empty() )
1917 {
1918 wxTreeItemId current = items.top();
1919 items.pop();
1920
1921 // Process the current item
1922 m_TreeProject->SetItemState( current, wxTREE_ITEMSTATE_NONE );
1923
1924 wxTreeItemIdValue cookie;
1925 wxTreeItemId child = m_TreeProject->GetFirstChild( current, cookie );
1926
1927 while( child.IsOk() )
1928 {
1929 items.push( child );
1930 child = m_TreeProject->GetNextChild( current, cookie );
1931 }
1932 }
1933}
1934
1935
1937{
1938 if( ADVANCED_CFG::GetCfg().m_EnableGit == false )
1939 return;
1940
1941 if( !m_TreeProject )
1942 return;
1943
1944 wxTimeSpan timeSinceLastUpdate = wxDateTime::Now() - m_lastGitStatusUpdate;
1945
1946 if( timeSinceLastUpdate.Abs() < wxTimeSpan::Seconds( 2 ) )
1947 return;
1948
1949 m_lastGitStatusUpdate = wxDateTime::Now();
1950
1951 wxTreeItemId kid = m_TreeProject->GetRootItem();
1952
1953 if( !kid.IsOk() )
1954 return;
1955
1956 git_repository* repo = m_TreeProject->GetGitRepo();
1957
1958 if( !repo )
1959 return;
1960
1961 // Get Current Branch
1962
1963 git_reference* currentBranchReference = nullptr;
1964 int rc = git_repository_head( &currentBranchReference, repo );
1965
1966 PROJECT_TREE_ITEM* rootItem = GetItemIdData( kid );
1967 wxFileName rootFilename( rootItem->GetFileName() );
1968 wxString repoWorkDir( git_repository_workdir( repo ) );
1969
1970 // Get the current branch name
1971 if( currentBranchReference )
1972 {
1973 wxString filename = wxFileNameFromPath( rootItem->GetFileName() );
1974 wxString branchName = git_reference_shorthand( currentBranchReference );
1975
1976 m_TreeProject->SetItemText( kid, filename + " [" + branchName + "]" );
1977 git_reference_free( currentBranchReference );
1978 }
1979 else if( rc == GIT_EUNBORNBRANCH )
1980 {
1981 // TODO: couldn't immediately figure out if libgit2 can return the name of an unborn branch
1982 // For now, just do nothing
1983 }
1984 else
1985 {
1986 if( giterr_last()->klass != m_gitLastError )
1987 wxLogError( "Failed to lookup current branch: %s", giterr_last()->message );
1988
1989 m_gitLastError = giterr_last()->klass;
1990 }
1991
1992 // Collect a map to easily set the state of each item
1993 std::map<wxString, wxTreeItemId> branchMap;
1994 {
1995 std::stack<wxTreeItemId> items;
1996 items.push( kid );
1997
1998 while( !items.empty() )
1999 {
2000 kid = items.top();
2001 items.pop();
2002
2003 PROJECT_TREE_ITEM* nextItem = GetItemIdData( kid );
2004
2005 wxString gitAbsPath = nextItem->GetFileName();
2006#ifdef _WIN32
2007 gitAbsPath.Replace( wxS( "\\" ), wxS( "/" ) );
2008#endif
2009 branchMap[gitAbsPath] = kid;
2010
2011 wxTreeItemIdValue cookie;
2012 wxTreeItemId child = m_TreeProject->GetFirstChild( kid, cookie );
2013
2014 while( child.IsOk() )
2015 {
2016 items.push( child );
2017 child = m_TreeProject->GetNextChild( kid, cookie );
2018 }
2019 }
2020 }
2021
2022 wxFileName relative = rootFilename;
2023 relative.MakeRelativeTo( repoWorkDir );
2024 wxString pathspecStr = relative.GetPath( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR );
2025
2026#ifdef _WIN32
2027 pathspecStr.Replace( wxS( "\\" ), wxS( "/" ) );
2028#endif
2029
2030 const char* pathspec[] = { pathspecStr.c_str().AsChar() };
2031
2032 git_status_options status_options;
2033 git_status_init_options( &status_options, GIT_STATUS_OPTIONS_VERSION );
2034 status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
2035 status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
2036 status_options.pathspec = { (char**) pathspec, 1 };
2037
2038 git_index* index = nullptr;
2039
2040 if( git_repository_index( &index, repo ) != GIT_OK )
2041 {
2042 m_gitLastError = giterr_last()->klass;
2043 wxLogTrace( traceGit, wxS( "Failed to get git index: %s" ), giterr_last()->message );
2044 return;
2045 }
2046
2047 git_status_list* status_list = nullptr;
2048
2049 if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK )
2050 {
2051 wxLogTrace( traceGit, wxS( "Failed to get git status list: %s" ), giterr_last()->message );
2052 git_index_free( index );
2053 return;
2054 }
2055
2056 auto [ localChanges, remoteChanges ] = m_TreeProject->GitCommon()->GetDifferentFiles();
2057
2058 size_t count = git_status_list_entrycount( status_list );
2059
2060 for( size_t ii = 0; ii < count; ++ii )
2061 {
2062 const git_status_entry* entry = git_status_byindex( status_list, ii );
2063 std::string path( entry->head_to_index? entry->head_to_index->old_file.path
2064 : entry->index_to_workdir->old_file.path );
2065
2066 wxString absPath = repoWorkDir;
2067 absPath << path;
2068
2069 auto iter = branchMap.find( absPath );
2070
2071 if( iter == branchMap.end() )
2072 continue;
2073
2074 // If we are current, don't continue because we still need to check to see if the
2075 // current commit is ahead/behind the remote. If the file is modified/added/deleted,
2076 // that is the main status we want to show.
2077 if( entry->status == GIT_STATUS_CURRENT )
2078 {
2079 m_TreeProject->SetItemState(
2080 iter->second,
2082 }
2083 else if( entry->status & ( GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED ) )
2084 {
2085 m_TreeProject->SetItemState(
2086 iter->second,
2088 continue;
2089 }
2090 else if( entry->status & ( GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW ) )
2091 {
2092 m_TreeProject->SetItemState(
2093 iter->second,
2094 static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_ADDED ) );
2095 continue;
2096 }
2097 else if( entry->status & ( GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED ) )
2098 {
2099 m_TreeProject->SetItemState(
2100 iter->second,
2102 continue;
2103 }
2104
2105 // Check if file is up to date with the remote
2106 if( localChanges.count( path ) )
2107 {
2108 m_TreeProject->SetItemState(
2109 iter->second,
2110 static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD ) );
2111 continue;
2112 }
2113 else if( remoteChanges.count( path ) )
2114 {
2115 m_TreeProject->SetItemState(
2116 iter->second,
2117 static_cast<int>( KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND ) );
2118 continue;
2119 }
2120 else
2121 {
2122 m_TreeProject->SetItemState(
2123 iter->second,
2125 continue;
2126 }
2127 }
2128
2129 git_status_list_free( status_list );
2130 git_index_free( index );
2131}
2132
2133
2134void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
2135{
2136 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
2137
2138 git_repository* repo = m_TreeProject->GetGitRepo();
2139
2140 if( repo == nullptr )
2141 {
2142 wxMessageBox( "The selected directory is not a git project." );
2143 return;
2144 }
2145
2146 git_config* config = nullptr;
2147 git_repository_config( &config, repo );
2148
2149 // Read relevant data from the git config
2150 wxString authorName;
2151 wxString authorEmail;
2152
2153 // Read author name
2154 git_config_entry* name_c = nullptr;
2155 git_config_entry* email_c = nullptr;
2156 int authorNameError = git_config_get_entry( &name_c, config, "user.name" );
2157
2158 if( authorNameError != 0 || name_c == nullptr )
2159 {
2160 authorName = Pgm().GetCommonSettings()->m_Git.authorName;
2161 }
2162 else
2163 {
2164 authorName = name_c->value;
2165 git_config_entry_free( name_c );
2166 }
2167
2168 // Read author email
2169 int authorEmailError = git_config_get_entry( &email_c, config, "user.email" );
2170
2171 if( authorEmailError != 0 || email_c == nullptr )
2172 {
2173 authorEmail = Pgm().GetCommonSettings()->m_Git.authorEmail;
2174 }
2175 else
2176 {
2177 authorEmail = email_c->value;
2178 git_config_entry_free( email_c );
2179 }
2180
2181 // Free the config object
2182 git_config_free( config );
2183
2184 // Collect modified files in the repository
2185 git_status_options status_options;
2186 git_status_init_options( &status_options, GIT_STATUS_OPTIONS_VERSION );
2187 status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
2188 status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
2189
2190 git_status_list* status_list = nullptr;
2191 git_status_list_new( &status_list, repo, &status_options );
2192
2193 std::map<wxString, int> modifiedFiles;
2194
2195 size_t count = git_status_list_entrycount( status_list );
2196
2197 std::set<wxString> selected_files;
2198
2199 for( PROJECT_TREE_ITEM* item : tree_data )
2200 {
2201 if( item->GetType() != TREE_FILE_TYPE::DIRECTORY )
2202 selected_files.emplace( item->GetFileName() );
2203 }
2204
2205 for( size_t i = 0; i < count; ++i )
2206 {
2207 const git_status_entry* entry = git_status_byindex( status_list, i );
2208
2209 // Check if the file is modified (index or workdir changes)
2210 if( entry->status == GIT_STATUS_CURRENT
2211 || ( entry->status & ( GIT_STATUS_CONFLICTED | GIT_STATUS_IGNORED ) ) )
2212 {
2213 continue;
2214 }
2215
2216 wxFileName fn;
2217 wxString filePath;
2218
2219 // TODO: we are kind of erasing the difference between workdir and index here,
2220 // because the Commit dialog doesn't show that difference.
2221 // Entry may only have a head_to_index if it was previously staged
2222 if( entry->index_to_workdir )
2223 {
2224 fn.Assign( entry->index_to_workdir->old_file.path );
2225 fn.MakeAbsolute( git_repository_workdir( repo ) );
2226 filePath = wxString( entry->index_to_workdir->old_file.path, wxConvUTF8 );
2227 }
2228 else if( entry->head_to_index )
2229 {
2230 fn.Assign( entry->head_to_index->old_file.path );
2231 fn.MakeAbsolute( git_repository_workdir( repo ) );
2232 filePath = wxString( entry->head_to_index->old_file.path, wxConvUTF8 );
2233 }
2234 else
2235 {
2236 wxCHECK2_MSG( false, continue, "File status with neither git_status_entry set!" );
2237 }
2238
2239 if( aEvent.GetId() == ID_GIT_COMMIT_PROJECT )
2240 {
2241 modifiedFiles.emplace( filePath, entry->status );
2242 }
2243 else if( selected_files.count( fn.GetFullPath() ) )
2244 {
2245 modifiedFiles.emplace( filePath, entry->status );
2246 }
2247 }
2248
2249 git_status_list_free( status_list );
2250
2251 // Create a commit dialog
2252 DIALOG_GIT_COMMIT dlg( wxGetTopLevelParent( this ), repo, authorName, authorEmail,
2253 modifiedFiles );
2254 auto ret = dlg.ShowModal();
2255
2256 if( ret == wxID_OK )
2257 {
2258 // Commit the changes
2259 git_oid tree_id;
2260 git_tree* tree = nullptr;
2261 git_commit* parent = nullptr;
2262 git_index* index = nullptr;
2263
2264 std::vector<wxString> files = dlg.GetSelectedFiles();
2265
2266 if( dlg.GetCommitMessage().IsEmpty() )
2267 {
2268 wxMessageBox( _( "Discarding commit due to empty commit message." ) );
2269 return;
2270 }
2271
2272 if( files.empty() )
2273 {
2274 wxMessageBox( _( "Discarding commit due to empty file selection." ) );
2275 return;
2276 }
2277
2278 if( git_repository_index( &index, repo ) != 0 )
2279 {
2280 wxMessageBox( wxString::Format( _( "Failed to get repository index: %s" ),
2281 giterr_last()->message ) );
2282 return;
2283 }
2284
2285 for( wxString& file :files )
2286 {
2287 if( git_index_add_bypath( index, file.mb_str() ) != 0 )
2288 {
2289 wxMessageBox( wxString::Format( _( "Failed to add file to index: %s" ),
2290 giterr_last()->message ) );
2291 git_index_free( index );
2292 return;
2293 }
2294 }
2295
2296 if( git_index_write( index ) != 0 )
2297 {
2298 wxMessageBox( wxString::Format( _( "Failed to write index: %s" ),
2299 giterr_last()->message ) );
2300 git_index_free( index );
2301 return;
2302 }
2303
2304 if( git_index_write_tree( &tree_id, index ) != 0)
2305 {
2306 wxMessageBox( wxString::Format( _( "Failed to write tree: %s" ),
2307 giterr_last()->message ) );
2308 git_index_free( index );
2309 return;
2310 }
2311
2312 git_index_free( index );
2313
2314 if( git_tree_lookup( &tree, repo, &tree_id ) != 0 )
2315 {
2316 wxMessageBox( wxString::Format( _( "Failed to lookup tree: %s" ),
2317 giterr_last()->message ) );
2318 return;
2319 }
2320
2321 git_reference* headRef = nullptr;
2322
2323 if( 0 == git_repository_head_unborn( repo ) )
2324 {
2325 if( git_repository_head( &headRef, repo ) != 0 )
2326 {
2327 wxMessageBox( wxString::Format( _( "Failed to get HEAD reference: %s" ),
2328 giterr_last()->message ) );
2329 git_index_free( index );
2330 return;
2331 }
2332
2333 if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
2334 {
2335 wxMessageBox( wxString::Format( _( "Failed to get commit: %s" ),
2336 giterr_last()->message ) );
2337 git_reference_free( headRef );
2338 git_index_free( index );
2339 return;
2340 }
2341
2342 git_reference_free( headRef );
2343 }
2344
2345 const wxString& commit_msg = dlg.GetCommitMessage();
2346 const wxString& author_name = dlg.GetAuthorName();
2347 const wxString& author_email = dlg.GetAuthorEmail();
2348
2349 git_signature* author = nullptr;
2350
2351 if( git_signature_now( &author, author_name.mb_str(), author_email.mb_str() ) != 0 )
2352 {
2353 wxMessageBox( wxString::Format( _( "Failed to create author signature: %s" ),
2354 giterr_last()->message ) );
2355 return;
2356 }
2357
2358 git_oid oid;
2359
2360#if( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \
2361 && ( LIBGIT2_VER_REVISION < 2 || LIBGIT2_VER_REVISION == 3 ) )
2362 /*
2363 * For libgit2 versions 1.8.0, 1.8.1. (cf19ddc52)
2364 * This change was reverted for 1.8.2 (49d3fadfc, main branch)
2365 * The revert for 1.8.2 was not included for 1.8.3 (which is on the maint/v1.8 branch, not main)
2366 * This change was also reverted for 1.8.4 (94ba816f6, also maint/v1.8 branch)
2367 *
2368 * As of 1.8.4, the history is like this:
2369 *
2370 * * 3f4182d15 (tag: v1.8.4, maint/v1.8)
2371 * * 94ba816f6 Revert "commit: fix const declaration" [puts const back]
2372 * * 3353f78e8 (tag: v1.8.3)
2373 * | * 4ce872a0f (tag: v1.8.2-rc1, tag: v1.8.2)
2374 * | * 49d3fadfc Revert "commit: fix const declaration" [puts const back]
2375 * |/
2376 * * 36f7e21ad (tag: v1.8.1)
2377 * * d74d49148 (tag: v1.8.0)
2378 * * cf19ddc52 commit: fix const declaration [removes const]
2379 */
2380 git_commit* const parents[1] = { parent };
2381#else
2382 // For libgit2 versions older than 1.8.0, or equal to 1.8.2, or 1.8.4+
2383 const git_commit* parents[1] = { parent };
2384#endif
2385
2386 if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr, commit_msg.mb_str(), tree,
2387 1, parents ) != 0 )
2388 {
2389 wxMessageBox( wxString::Format( _( "Failed to create commit: %s" ),
2390 giterr_last()->message ) );
2391 return;
2392 }
2393
2394 git_signature_free( author );
2395 git_commit_free( parent );
2396 git_tree_free( tree );
2397 }
2398}
2399
2400
2401void PROJECT_TREE_PANE::onGitAddToIndex( wxCommandEvent& aEvent )
2402{
2403
2404}
2405
2406
2407bool PROJECT_TREE_PANE::canFileBeAddedToVCS( const wxString& aFile )
2408{
2409 git_index *index;
2410 size_t entry_pos;
2411
2412 git_repository* repo = m_TreeProject->GetGitRepo();
2413
2414 if( !repo )
2415 return false;
2416
2417 if( git_repository_index( &index, repo ) != 0 )
2418 return false;
2419
2420 // If we successfully find the file in the index, we may not add it to the VCS
2421 if( git_index_find( &entry_pos, index, aFile.mb_str() ) == 0 )
2422 {
2423 git_index_free( index );
2424 return false;
2425 }
2426
2427 git_index_free( index );
2428 return true;
2429}
2430
2431
2432void PROJECT_TREE_PANE::onGitSyncProject( wxCommandEvent& aEvent )
2433{
2434 git_repository* repo = m_TreeProject->GetGitRepo();
2435
2436 if( !repo )
2437 return;
2438
2439 GIT_SYNC_HANDLER handler( repo );
2440 handler.PerformSync();
2441}
2442
2443
2444void PROJECT_TREE_PANE::onGitFetch( wxCommandEvent& aEvent )
2445{
2446 KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon();
2447
2448 if( !gitCommon )
2449 return;
2450
2451 GIT_PULL_HANDLER handler( gitCommon );
2452 handler.PerformFetch();
2453}
2454
2455
2456void PROJECT_TREE_PANE::onGitResolveConflict( wxCommandEvent& aEvent )
2457{
2458 git_repository* repo = m_TreeProject->GetGitRepo();
2459
2460 if( !repo )
2461 return;
2462
2463 GIT_RESOLVE_CONFLICT_HANDLER handler( repo );
2464 handler.PerformResolveConflict();
2465}
2466
2467
2468void PROJECT_TREE_PANE::onGitRevertLocal( wxCommandEvent& aEvent )
2469{
2470 git_repository* repo = m_TreeProject->GetGitRepo();
2471
2472 if( !repo )
2473 return;
2474
2475 GIT_REVERT_HANDLER handler( repo );
2476 handler.PerformRevert();
2477}
2478
2479
2480void PROJECT_TREE_PANE::onGitRemoveFromIndex( wxCommandEvent& aEvent )
2481{
2482 git_repository* repo = m_TreeProject->GetGitRepo();
2483
2484 if( !repo )
2485 return;
2486
2487 GIT_REMOVE_FROM_INDEX_HANDLER handler( repo );
2488 handler.PerformRemoveFromIndex();
2489}
2490
2491
2493{
2494
2495}
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)
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 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 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_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:398
wxLogTrace helper definitions.
TREE_FILE_TYPE
Definition of file extensions used in Kicad.