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#include <wx/timer.h>
35#include <wx/wupdlock.h>
36
37#include <advanced_config.h>
38#include <bitmaps.h>
39#include <bitmap_store.h>
40#include <confirm.h>
43#include <gestfich.h>
44#include <macros.h>
45#include <trace_helpers.h>
48#include <core/kicad_algo.h>
49#include <paths.h>
51#include <scoped_set_reset.h>
52#include <string_utils.h>
53#include <thread_pool.h>
54#include <launch_ext.h>
55#include <wx/dcclient.h>
56#include <wx/progdlg.h>
57#include <wx/settings.h>
58
74
76
77#include "project_tree_item.h"
78#include "project_tree.h"
79#include "pgm_kicad.h"
80#include "kicad_id.h"
81#include "kicad_manager_frame.h"
82
83#include "project_tree_pane.h"
84#include <widgets/kistatusbar.h>
85
86#include <kiplatform/io.h>
87#include <kiplatform/secrets.h>
88
89
90/* Note about the project tree build process:
91 * Building the project tree can be *very* long if there are a lot of subdirectories in the
92 * working directory. Unfortunately, this happens easily if the project file *.pro is in the
93 * user's home directory.
94 * So the tree project is built "on demand":
95 * First the tree is built from the current directory and shows files and subdirs.
96 * > First level subdirs trees are built (i.e subdirs contents are not read)
97 * > When expanding a subdir, each subdir contains is read, and the corresponding sub tree is
98 * populated on the fly.
99 */
100
101// list of files extensions listed in the tree project window
102// Add extensions in a compatible regex format to see others files types
103static const wxChar* s_allowedExtensionsToList[] = {
104 wxT( "^.*\\.pro$" ),
105 wxT( "^.*\\.kicad_pro$" ),
106 wxT( "^.*\\.pdf$" ),
107 wxT( "^.*\\.sch$" ), // Legacy Eeschema files
108 wxT( "^.*\\.kicad_sch$" ), // S-expr Eeschema files
109 wxT( "^[^$].*\\.brd$" ), // Legacy Pcbnew files
110 wxT( "^[^$].*\\.kicad_pcb$" ), // S format Pcbnew board files
111 wxT( "^[^$].*\\.kicad_dru$" ), // Design rule files
112 wxT( "^[^$].*\\.kicad_wks$" ), // S format kicad drawing sheet files
113 wxT( "^[^$].*\\.kicad_mod$" ), // S format kicad footprint files, currently not listed
114 wxT( "^.*\\.net$" ), // pcbnew netlist file
115 wxT( "^.*\\.cir$" ), // Spice netlist file
116 wxT( "^.*\\.lib$" ), // Legacy schematic library file
117 wxT( "^.*\\.kicad_sym$" ), // S-expr symbol libraries
118 wxT( "^.*\\.txt$" ), // Text files
119 wxT( "^.*\\.md$" ), // Markdown files
120 wxT( "^.*\\.pho$" ), // Gerber file (Old Kicad extension)
121 wxT( "^.*\\.gbr$" ), // Gerber file
122 wxT( "^.*\\.gbrjob$" ), // Gerber job file
123 wxT( "^.*\\.gb[alops]$" ), // Gerber back (or bottom) layer file (deprecated Protel ext)
124 wxT( "^.*\\.gt[alops]$" ), // Gerber front (or top) layer file (deprecated Protel ext)
125 wxT( "^.*\\.g[0-9]{1,2}$" ), // Gerber inner layer file (deprecated Protel ext)
126 wxT( "^.*\\.gm[0-9]{1,2}$" ), // Gerber mechanical layer file (deprecated Protel ext)
127 wxT( "^.*\\.gko$" ), // Gerber keepout layer file (deprecated Protel ext)
128 wxT( "^.*\\.odt$" ),
129 wxT( "^.*\\.htm$" ),
130 wxT( "^.*\\.html$" ),
131 wxT( "^.*\\.rpt$" ), // Report files
132 wxT( "^.*\\.csv$" ), // Report files in comma separated format
133 wxT( "^.*\\.pos$" ), // Footprint position files
134 wxT( "^.*\\.cmp$" ), // CvPcb cmp/footprint link files
135 wxT( "^.*\\.drl$" ), // Excellon drill files
136 wxT( "^.*\\.nc$" ), // Excellon NC drill files (alternate file ext)
137 wxT( "^.*\\.xnc$" ), // Excellon NC drill files (alternate file ext)
138 wxT( "^.*\\.svg$" ), // SVG print/plot files
139 wxT( "^.*\\.ps$" ), // PostScript plot files
140 wxT( "^.*\\.zip$" ), // Zip archive files
141 wxT( "^.*\\.kicad_jobset" ), // KiCad jobs file
142 nullptr // end of list
143};
144
145
160
161 ID_GIT_INITIALIZE_PROJECT, // Initialize a new git repository in an existing project
162 ID_GIT_CLONE_PROJECT, // Clone a project from a remote repository
163 ID_GIT_COMMIT_PROJECT, // Commit all files in the project
164 ID_GIT_COMMIT_FILE, // Commit a single file
165 ID_GIT_SYNC_PROJECT, // Sync the project with the remote repository (pull and push -- same as Update)
166 ID_GIT_FETCH, // Fetch the remote repository (without merging -- this is the same as Refresh)
167 ID_GIT_PUSH, // Push the local repository to the remote repository
168 ID_GIT_PULL, // Pull the remote repository to the local repository
169 ID_GIT_RESOLVE_CONFLICT, // Present the user with a resolve conflicts dialog (ours/theirs/merge)
170 ID_GIT_REVERT_LOCAL, // Revert the local repository to the last commit
171 ID_GIT_COMPARE, // Compare the current project to a different branch or commit in the git repository
172 ID_GIT_REMOVE_VCS, // Remove the git repository data from the project directory (rm .git)
173 ID_GIT_ADD_TO_INDEX, // Add a file to the git index
174 ID_GIT_REMOVE_FROM_INDEX, // Remove a file from the git index
175 ID_GIT_SWITCH_BRANCH, // Switch the local repository to a different branch
176 ID_GIT_SWITCH_QUICK1, // Switch the local repository to the first quick branch
177 ID_GIT_SWITCH_QUICK2, // Switch the local repository to the second quick branch
178 ID_GIT_SWITCH_QUICK3, // Switch the local repository to the third quick branch
179 ID_GIT_SWITCH_QUICK4, // Switch the local repository to the fourth quick branch
180 ID_GIT_SWITCH_QUICK5, // Switch the local repository to the fifth quick branch
181
183};
184
185
186BEGIN_EVENT_TABLE( PROJECT_TREE_PANE, wxSashLayoutWindow )
187 EVT_TREE_ITEM_ACTIVATED( ID_PROJECT_TREE, PROJECT_TREE_PANE::onSelect )
188 EVT_TREE_ITEM_EXPANDED( ID_PROJECT_TREE, PROJECT_TREE_PANE::onExpand )
189 EVT_TREE_ITEM_RIGHT_CLICK( ID_PROJECT_TREE, PROJECT_TREE_PANE::onRight )
196
212
214
215 EVT_IDLE( PROJECT_TREE_PANE::onIdle )
216 EVT_PAINT( PROJECT_TREE_PANE::onPaint )
217END_EVENT_TABLE()
218
219
220wxDECLARE_EVENT( UPDATE_ICONS, wxCommandEvent );
221
223 wxSashLayoutWindow( parent, ID_LEFT_FRAME, wxDefaultPosition, wxDefaultSize,
224 wxNO_BORDER | wxTAB_TRAVERSAL )
225{
226 m_Parent = parent;
227 m_TreeProject = nullptr;
228 m_isRenaming = false;
229 m_selectedItem = nullptr;
230 m_watcherNeedReset = false;
231 m_gitLastError = GIT_ERROR_NONE;
232 m_watcher = nullptr;
233 m_gitIconsInitialized = false;
234
235 Bind( wxEVT_FSWATCHER,
236 wxFileSystemWatcherEventHandler( PROJECT_TREE_PANE::onFileSystemEvent ), this );
237
238 Bind( wxEVT_SYS_COLOUR_CHANGED,
239 wxSysColourChangedEventHandler( PROJECT_TREE_PANE::onThemeChanged ), this );
240
241 m_gitSyncTimer.SetOwner( this );
242 m_gitStatusTimer.SetOwner( this );
243 Bind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitSyncTimer ), this, m_gitSyncTimer.GetId() );
244 Bind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitStatusTimer ), this, m_gitStatusTimer.GetId() );
245 /*
246 * Filtering is now inverted: the filters are actually used to _enable_ support
247 * for a given file type.
248 */
249 for( int ii = 0; s_allowedExtensionsToList[ii] != nullptr; ii++ )
250 m_filters.emplace_back( s_allowedExtensionsToList[ii] );
251
252 m_filters.emplace_back( wxT( "^no KiCad files found" ) );
253
254 ReCreateTreePrj();
255}
256
257
259{
260 Unbind( wxEVT_FSWATCHER,
261 wxFileSystemWatcherEventHandler( PROJECT_TREE_PANE::onFileSystemEvent ), this );
262 Unbind( wxEVT_SYS_COLOUR_CHANGED,
263 wxSysColourChangedEventHandler( PROJECT_TREE_PANE::onThemeChanged ), this );
264
265 m_gitSyncTimer.Stop();
266 m_gitStatusTimer.Stop();
267 Unbind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitSyncTimer ), this,
268 m_gitSyncTimer.GetId() );
269 Unbind( wxEVT_TIMER, wxTimerEventHandler( PROJECT_TREE_PANE::onGitStatusTimer ), this,
270 m_gitStatusTimer.GetId() );
272}
273
274
276{
277 if( m_watcher )
278 {
279 m_watcher->RemoveAll();
280 m_watcher->SetOwner( nullptr );
281 delete m_watcher;
282 m_watcher = nullptr;
283 }
284}
285
286
288{
289 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
290
291 if( tree_data.size() != 1 )
292 return;
293
294 wxString prj_filename = tree_data[0]->GetFileName();
295
296 m_Parent->LoadProject( prj_filename );
297}
298
299
300void PROJECT_TREE_PANE::onOpenDirectory( wxCommandEvent& event )
301{
302 // Get the root directory name:
303 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
304
305 for( PROJECT_TREE_ITEM* item_data : tree_data )
306 {
307 // Ask for the new sub directory name
308 wxString curr_dir = item_data->GetDir();
309
310 if( curr_dir.IsEmpty() )
311 {
312 // Use project path if the tree view path was empty.
313 curr_dir = wxPathOnly( m_Parent->GetProjectFileName() );
314
315 // As a last resort use the user's documents folder.
316 if( curr_dir.IsEmpty() || !wxFileName::DirExists( curr_dir ) )
318
319 if( !curr_dir.IsEmpty() )
320 curr_dir += wxFileName::GetPathSeparator();
321 }
322
323 LaunchExternal( curr_dir );
324 }
325}
326
327
328void PROJECT_TREE_PANE::onCreateNewDirectory( wxCommandEvent& event )
329{
330 // Get the root directory name:
331 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
332
333 for( PROJECT_TREE_ITEM* item_data : tree_data )
334 {
335 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
336
337 // Ask for the new sub directory name
338 wxString curr_dir = item_data->GetDir();
339
340 if( curr_dir.IsEmpty() )
341 curr_dir = prj_dir;
342
343 wxString new_dir = wxGetTextFromUser( _( "Directory name:" ), _( "Create New Directory" ) );
344
345 if( new_dir.IsEmpty() )
346 return;
347
348 wxString full_dirname = curr_dir + wxFileName::GetPathSeparator() + new_dir;
349
350 if( !wxMkdir( full_dirname ) )
351 return;
352
353 addItemToProjectTree( full_dirname, item_data->GetId(), nullptr, false );
354 }
355}
356
357
359{
360 switch( type )
361 {
362 case TREE_FILE_TYPE::LEGACY_PROJECT: return FILEEXT::LegacyProjectFileExtension;
363 case TREE_FILE_TYPE::JSON_PROJECT: return FILEEXT::ProjectFileExtension;
364 case TREE_FILE_TYPE::LEGACY_SCHEMATIC: return FILEEXT::LegacySchematicFileExtension;
365 case TREE_FILE_TYPE::SEXPR_SCHEMATIC: return FILEEXT::KiCadSchematicFileExtension;
366 case TREE_FILE_TYPE::LEGACY_PCB: return FILEEXT::LegacyPcbFileExtension;
367 case TREE_FILE_TYPE::SEXPR_PCB: return FILEEXT::KiCadPcbFileExtension;
368 case TREE_FILE_TYPE::GERBER: return FILEEXT::GerberFileExtensionsRegex;
369 case TREE_FILE_TYPE::GERBER_JOB_FILE: return FILEEXT::GerberJobFileExtension;
370 case TREE_FILE_TYPE::HTML: return FILEEXT::HtmlFileExtension;
371 case TREE_FILE_TYPE::PDF: return FILEEXT::PdfFileExtension;
372 case TREE_FILE_TYPE::TXT: return FILEEXT::TextFileExtension;
373 case TREE_FILE_TYPE::MD: return FILEEXT::MarkdownFileExtension;
374 case TREE_FILE_TYPE::NET: return FILEEXT::NetlistFileExtension;
375 case TREE_FILE_TYPE::NET_SPICE: return FILEEXT::SpiceFileExtension;
376 case TREE_FILE_TYPE::CMP_LINK: return FILEEXT::FootprintAssignmentFileExtension;
377 case TREE_FILE_TYPE::REPORT: return FILEEXT::ReportFileExtension;
378 case TREE_FILE_TYPE::FP_PLACE: return FILEEXT::FootprintPlaceFileExtension;
379 case TREE_FILE_TYPE::DRILL: return FILEEXT::DrillFileExtension;
380 case TREE_FILE_TYPE::DRILL_NC: return "nc";
381 case TREE_FILE_TYPE::DRILL_XNC: return "xnc";
382 case TREE_FILE_TYPE::SVG: return FILEEXT::SVGFileExtension;
383 case TREE_FILE_TYPE::DRAWING_SHEET: return FILEEXT::DrawingSheetFileExtension;
384 case TREE_FILE_TYPE::FOOTPRINT_FILE: return FILEEXT::KiCadFootprintFileExtension;
385 case TREE_FILE_TYPE::SCHEMATIC_LIBFILE: return FILEEXT::LegacySymbolLibFileExtension;
386 case TREE_FILE_TYPE::SEXPR_SYMBOL_LIB_FILE: return FILEEXT::KiCadSymbolLibFileExtension;
387 case TREE_FILE_TYPE::DESIGN_RULES: return FILEEXT::DesignRulesFileExtension;
388 case TREE_FILE_TYPE::ZIP_ARCHIVE: return FILEEXT::ArchiveFileExtension;
389 case TREE_FILE_TYPE::JOBSET_FILE: return FILEEXT::KiCadJobSetFileExtension;
390
391 case TREE_FILE_TYPE::ROOT:
392 case TREE_FILE_TYPE::UNKNOWN:
393 case TREE_FILE_TYPE::MAX:
394 case TREE_FILE_TYPE::DIRECTORY: break;
395 }
396
397 return wxEmptyString;
398}
399
400
401std::vector<wxString> getProjects( const wxDir& dir )
402{
403 std::vector<wxString> projects;
404 wxString dir_filename;
405 bool haveFile = dir.GetFirst( &dir_filename );
406
407 while( haveFile )
408 {
409 wxFileName file( dir_filename );
410
411 if( file.GetExt() == FILEEXT::LegacyProjectFileExtension
412 || file.GetExt() == FILEEXT::ProjectFileExtension )
413 projects.push_back( file.GetName() );
414
415 haveFile = dir.GetNext( &dir_filename );
416 }
417
418 return projects;
419}
420
421
422
423
424
425wxTreeItemId PROJECT_TREE_PANE::addItemToProjectTree( const wxString& aName,
426 const wxTreeItemId& aParent,
427 std::vector<wxString>* aProjectNames,
428 bool aRecurse )
429{
430 TREE_FILE_TYPE type = TREE_FILE_TYPE::UNKNOWN;
431 wxFileName fn( aName );
432
433 if( KIPLATFORM::IO::IsFileHidden( aName ) )
434 return wxTreeItemId();
435
436 if( wxDirExists( aName ) )
437 {
438 type = TREE_FILE_TYPE::DIRECTORY;
439 }
440 else
441 {
442 // Filter
443 wxRegEx reg;
444 bool addFile = false;
445
446 for( const wxString& m_filter : m_filters )
447 {
448 wxCHECK2_MSG( reg.Compile( m_filter, wxRE_ICASE ), continue,
449 wxString::Format( "Regex %s failed to compile.", m_filter ) );
450
451 if( reg.Matches( aName ) )
452 {
453 addFile = true;
454 break;
455 }
456 }
457
458 if( !addFile )
459 return wxTreeItemId();
460
461 for( int i = static_cast<int>( TREE_FILE_TYPE::LEGACY_PROJECT );
462 i < static_cast<int>( TREE_FILE_TYPE::MAX ); i++ )
463 {
464 wxString ext = GetFileExt( (TREE_FILE_TYPE) i );
465
466 if( ext == wxT( "" ) )
467 continue;
468
469 if( reg.Compile( wxString::FromAscii( "^.*\\." ) + ext + wxString::FromAscii( "$" ),
470 wxRE_ICASE ) && reg.Matches( aName ) )
471 {
472 type = (TREE_FILE_TYPE) i;
473 break;
474 }
475 }
476 }
477
478 wxString file = wxFileNameFromPath( aName );
479 wxFileName currfile( file );
480 wxFileName project( m_Parent->GetProjectFileName() );
481
482 // Ignore legacy projects with the same name as the current project
483 if( ( type == TREE_FILE_TYPE::LEGACY_PROJECT )
484 && ( currfile.GetName().CmpNoCase( project.GetName() ) == 0 ) )
485 {
486 return wxTreeItemId();
487 }
488
489 if( currfile.GetExt() == GetFileExt( TREE_FILE_TYPE::LEGACY_SCHEMATIC )
490 || currfile.GetExt() == GetFileExt( TREE_FILE_TYPE::SEXPR_SCHEMATIC ) )
491 {
492 if( aProjectNames )
493 {
494 if( !alg::contains( *aProjectNames, currfile.GetName() ) )
495 return wxTreeItemId();
496 }
497 else
498 {
499 PROJECT_TREE_ITEM* parentTreeItem = GetItemIdData( aParent );
500 wxDir parentDir( parentTreeItem->GetDir() );
501 std::vector<wxString> projects = getProjects( parentDir );
502
503 if( !alg::contains( projects, currfile.GetName() ) )
504 return wxTreeItemId();
505 }
506 }
507
508 // also check to see if it is already there.
509 wxTreeItemIdValue cookie;
510 wxTreeItemId kid = m_TreeProject->GetFirstChild( aParent, cookie );
511
512 while( kid.IsOk() )
513 {
514 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
515
516 if( itemData && itemData->GetFileName() == aName )
517 return itemData->GetId(); // well, we would have added it, but it is already here!
518
519 kid = m_TreeProject->GetNextChild( aParent, cookie );
520 }
521
522 // Only show current files if both legacy and current files are present
523 if( type == TREE_FILE_TYPE::LEGACY_PROJECT || type == TREE_FILE_TYPE::JSON_PROJECT
524 || type == TREE_FILE_TYPE::LEGACY_SCHEMATIC || type == TREE_FILE_TYPE::SEXPR_SCHEMATIC )
525 {
526 kid = m_TreeProject->GetFirstChild( aParent, cookie );
527
528 while( kid.IsOk() )
529 {
530 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
531
532 if( itemData )
533 {
534 wxFileName fname( itemData->GetFileName() );
535
536 if( fname.GetName().CmpNoCase( currfile.GetName() ) == 0 )
537 {
538 switch( type )
539 {
540 case TREE_FILE_TYPE::LEGACY_PROJECT:
541 if( itemData->GetType() == TREE_FILE_TYPE::JSON_PROJECT )
542 return wxTreeItemId();
543
544 break;
545
546 case TREE_FILE_TYPE::LEGACY_SCHEMATIC:
547 if( itemData->GetType() == TREE_FILE_TYPE::SEXPR_SCHEMATIC )
548 return wxTreeItemId();
549
550 break;
551
552 case TREE_FILE_TYPE::JSON_PROJECT:
553 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_PROJECT )
554 m_TreeProject->Delete( kid );
555
556 break;
557
558 case TREE_FILE_TYPE::SEXPR_SCHEMATIC:
559 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_SCHEMATIC )
560 m_TreeProject->Delete( kid );
561
562 break;
563
564 default:
565 break;
566 }
567 }
568 }
569
570 kid = m_TreeProject->GetNextChild( aParent, cookie );
571 }
572 }
573
574 // Append the item (only appending the filename not the full path):
575 wxTreeItemId newItemId = m_TreeProject->AppendItem( aParent, file );
576 PROJECT_TREE_ITEM* data = new PROJECT_TREE_ITEM( type, aName, m_TreeProject );
577
578 m_TreeProject->SetItemData( newItemId, data );
579 data->SetState( 0 );
580
581 // Mark root files (files which have the same aName as the project)
582 wxString fileName = currfile.GetName().Lower();
583 wxString projName = project.GetName().Lower();
584
585 if( fileName == projName || fileName.StartsWith( projName + "-" ) )
586 data->SetRootFile( true );
587
588#ifndef __WINDOWS__
589 bool subdir_populated = false;
590#endif
591
592 // This section adds dirs and files found in the subdirs
593 // in this case AddFile is recursive, but for the first level only.
594 if( TREE_FILE_TYPE::DIRECTORY == type && aRecurse )
595 {
596 wxDir dir( aName );
597
598 if( dir.IsOpened() ) // protected dirs will not open properly.
599 {
600 std::vector<wxString> projects = getProjects( dir );
601 wxString dir_filename;
602 bool haveFile = dir.GetFirst( &dir_filename );
603
604 data->SetPopulated( true );
605
606#ifndef __WINDOWS__
607 subdir_populated = aRecurse;
608#endif
609
610 while( haveFile )
611 {
612 // Add name in tree, but do not recurse
613 wxString path = aName + wxFileName::GetPathSeparator() + dir_filename;
614 addItemToProjectTree( path, newItemId, &projects, false );
615
616 haveFile = dir.GetNext( &dir_filename );
617 }
618 }
619
620 // Sort filenames by alphabetic order
621 m_TreeProject->SortChildren( newItemId );
622 }
623
624#ifndef __WINDOWS__
625 if( subdir_populated )
626 m_watcherNeedReset = true;
627#endif
628
629 return newItemId;
630}
631
632
634{
635 std::lock_guard<std::mutex> lock1( m_gitStatusMutex );
636 std::lock_guard<std::mutex> lock2( m_gitTreeCacheMutex );
638
639 tp.wait_for_tasks();
640 m_gitStatusTimer.Stop();
641 m_gitSyncTimer.Stop();
642 m_gitTreeCache.clear();
643 m_gitStatusIcons.clear();
644
645 wxString pro_dir = m_Parent->GetProjectFileName();
646
647 if( !m_TreeProject )
648 m_TreeProject = new PROJECT_TREE( this );
649 else
650 m_TreeProject->DeleteAllItems();
651
652 if( !pro_dir ) // This is empty from PROJECT_TREE_PANE constructor
653 return;
654
656 {
657 git_repository* repo = m_TreeProject->GetGitRepo();
659 m_TreeProject->SetGitRepo( nullptr );
660 m_gitIconsInitialized = false;
661 }
662
663 wxFileName fn = pro_dir;
664 bool prjReset = false;
665
666 if( !fn.IsOk() )
667 {
668 fn.Clear();
669 fn.SetPath( PATHS::GetDefaultUserProjectsPath() );
670 fn.SetName( NAMELESS_PROJECT );
672 prjReset = true;
673 }
674
675 bool prjOpened = fn.FileExists();
676
677 // Bind the git repository to the project tree (if it exists)
678 if( Pgm().GetCommonSettings()->m_Git.enableGit )
679 {
681
683 {
684 m_TreeProject->GitCommon()->SetUsername( Prj().GetLocalSettings().m_GitRepoUsername );
685 m_TreeProject->GitCommon()->SetSSHKey( Prj().GetLocalSettings().m_GitSSHKey );
687 }
688 }
689
690 // We may have opened a legacy project, in which case GetProjectFileName will return the
691 // name of the migrated (new format) file, which may not have been saved to disk yet.
692 if( !prjOpened && !prjReset )
693 {
695 prjOpened = fn.FileExists();
696
697 // Set the ext back so that in the tree view we see the (not-yet-saved) new file
699 }
700
701 // root tree:
702 m_root = m_TreeProject->AddRoot( fn.GetFullName(), static_cast<int>( TREE_FILE_TYPE::ROOT ),
703 static_cast<int>( TREE_FILE_TYPE::ROOT ) );
704 m_TreeProject->SetItemBold( m_root, true );
705
706 // The main project file is now a JSON file
707 PROJECT_TREE_ITEM* data = new PROJECT_TREE_ITEM( TREE_FILE_TYPE::JSON_PROJECT, fn.GetFullPath(),
709
710 m_TreeProject->SetItemData( m_root, data );
711
712 // Now adding all current files if available
713 if( prjOpened )
714 {
715 pro_dir = wxPathOnly( m_Parent->GetProjectFileName() );
716 wxDir dir( pro_dir );
717
718 if( dir.IsOpened() ) // protected dirs will not open, see "man opendir()"
719 {
720 std::vector<wxString> projects = getProjects( dir );
721 wxString filename;
722 bool haveFile = dir.GetFirst( &filename );
723
724 while( haveFile )
725 {
726 if( filename != fn.GetFullName() )
727 {
728 wxString name = dir.GetName() + wxFileName::GetPathSeparator() + filename;
729
730 // Add items living in the project directory, and populate the item
731 // if it is a directory (sub directories will be not populated)
732 addItemToProjectTree( name, m_root, &projects, true );
733 }
734
735 haveFile = dir.GetNext( &filename );
736 }
737 }
738 }
739 else
740 {
741 m_TreeProject->AppendItem( m_root, wxT( "Empty project" ) );
742 }
743
744 m_TreeProject->Expand( m_root );
745
746 // Sort filenames by alphabetic order
747 m_TreeProject->SortChildren( m_root );
748
749 CallAfter(
750 [this] ()
751 {
752 wxLogTrace( traceGit, "PROJECT_TREE_PANE::ReCreateTreePrj: starting timers" );
753 m_gitSyncTimer.Start( 100, wxTIMER_ONE_SHOT );
754 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
755 } );
756}
757
758
760{
761 if( !m_TreeProject->GetGitRepo() )
762 return false;
763
764 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
765 return statusHandler.HasChangedFiles();
766}
767
768
769void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event )
770{
771 wxTreeItemId curr_item = Event.GetItem();
772
773 // Ensure item is selected (Under Windows right click does not select the item)
774 m_TreeProject->SelectItem( curr_item );
775
776 std::vector<PROJECT_TREE_ITEM*> selection = GetSelectedData();
778 wxFileName prj_dir( Prj().GetProjectPath(), wxEmptyString );
779 wxFileName git_dir( git->GetGitRootDirectory(), wxEmptyString );
780 prj_dir.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_CASE | wxPATH_NORM_DOTS
781 | wxPATH_NORM_ENV_VARS | wxPATH_NORM_TILDE );
782 git_dir.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_CASE | wxPATH_NORM_DOTS
783 | wxPATH_NORM_ENV_VARS | wxPATH_NORM_TILDE );
784 wxString prj_name = prj_dir.GetFullPath();
785 wxString git_name = git_dir.GetFullPath();
786
787 bool can_switch_to_project = true;
788 bool can_create_new_directory = true;
789 bool can_open_this_directory = true;
790 bool can_edit = true;
791 bool can_rename = true;
792 bool can_delete = true;
793 bool run_jobs = false;
794
795 bool vcs_has_repo = m_TreeProject->GetGitRepo() != nullptr;
796 bool vcs_can_commit = hasChangedFiles();
797 bool vcs_can_init = !vcs_has_repo;
798 bool vcs_can_remove = vcs_has_repo && git_name.StartsWith( prj_name ); // This means the .git is a subdirectory of the project
799 bool vcs_can_fetch = vcs_has_repo && git->HasPushAndPullRemote();
800 bool vcs_can_push = vcs_can_fetch && git->HasLocalCommits();
801 bool vcs_can_pull = vcs_can_fetch;
802 bool vcs_can_switch = vcs_has_repo;
803 bool vcs_menu = Pgm().GetCommonSettings()->m_Git.enableGit;
804
805 // Check if the libgit2 library has been successfully initialized
806#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
807 int major, minor, rev;
808 bool libgit_init = ( git_libgit2_version( &major, &minor, &rev ) == GIT_OK );
809#else
810 //Work around libgit2 API change for supporting older platforms
811 bool libgit_init = true;
812#endif
813
814 vcs_menu &= libgit_init;
815
816 if( selection.size() == 0 )
817 return;
818
819 // Remove things that don't make sense for multiple selections
820 if( selection.size() != 1 )
821 {
822 can_switch_to_project = false;
823 can_create_new_directory = false;
824 can_rename = false;
825 }
826
827 for( PROJECT_TREE_ITEM* item : selection )
828 {
829 // Check for empty project
830 if( !item )
831 {
832 can_switch_to_project = false;
833 can_edit = false;
834 can_rename = false;
835 continue;
836 }
837
838 can_delete = item->CanDelete();
839 can_rename = item->CanRename();
840
841 switch( item->GetType() )
842 {
843 case TREE_FILE_TYPE::JSON_PROJECT:
844 case TREE_FILE_TYPE::LEGACY_PROJECT:
845 can_rename = false;
846
847 if( item->GetId() == m_TreeProject->GetRootItem() )
848 {
849 can_switch_to_project = false;
850 }
851 else
852 {
853 can_create_new_directory = false;
854 can_open_this_directory = false;
855 }
856 break;
857
858 case TREE_FILE_TYPE::DIRECTORY:
859 can_switch_to_project = false;
860 can_edit = false;
861 break;
862
863 case TREE_FILE_TYPE::ZIP_ARCHIVE:
864 case TREE_FILE_TYPE::PDF:
865 can_edit = false;
866 can_switch_to_project = false;
867 can_create_new_directory = false;
868 can_open_this_directory = false;
869 break;
870
871 case TREE_FILE_TYPE::JOBSET_FILE:
872 run_jobs = true;
873 can_edit = false;
875
876 case TREE_FILE_TYPE::SEXPR_SCHEMATIC:
877 case TREE_FILE_TYPE::SEXPR_PCB:
879
880 default:
881 can_switch_to_project = false;
882 can_create_new_directory = false;
883 can_open_this_directory = false;
884
885 break;
886 }
887 }
888
889 wxMenu popup_menu;
890 wxString text;
891 wxString help_text;
892
893 if( can_switch_to_project )
894 {
895 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_SWITCH_TO_OTHER, _( "Switch to this Project" ),
896 _( "Close all editors, and switch to the selected project" ),
897 KiBitmap( BITMAPS::open_project ) );
898 popup_menu.AppendSeparator();
899 }
900
901 if( can_create_new_directory )
902 {
903 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_NEWDIR, _( "New Directory..." ),
904 _( "Create a New Directory" ), KiBitmap( BITMAPS::directory ) );
905 }
906
907 if( can_open_this_directory )
908 {
909 if( selection.size() == 1 )
910 {
911#ifdef __APPLE__
912 text = _( "Reveal in Finder" );
913 help_text = _( "Reveals the directory in a Finder window" );
914#else
915 text = _( "Open Directory in File Explorer" );
916 help_text = _( "Opens the directory in the default system file manager" );
917#endif
918 }
919 else
920 {
921#ifdef __APPLE__
922 text = _( "Reveal in Finder" );
923 help_text = _( "Reveals the directories in a Finder window" );
924#else
925 text = _( "Open Directories in File Explorer" );
926 help_text = _( "Opens the directories in the default system file manager" );
927#endif
928 }
929
930 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_OPEN_DIR, text, help_text,
931 KiBitmap( BITMAPS::directory_browser ) );
932 }
933
934 if( can_edit )
935 {
936 if( selection.size() == 1 )
937 help_text = _( "Open the file in a Text Editor" );
938 else
939 help_text = _( "Open files in a Text Editor" );
940
941 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_TXTEDIT, _( "Edit in a Text Editor" ), help_text,
942 KiBitmap( BITMAPS::editor ) );
943 }
944
945 if( run_jobs && selection.size() == 1 )
946 {
947 KIUI::AddMenuItem( &popup_menu, ID_JOBS_RUN, _( "Run Jobs" ), help_text,
948 KiBitmap( BITMAPS::exchange ) );
949 }
950
951 if( can_rename )
952 {
953 if( selection.size() == 1 )
954 {
955 text = _( "Rename File..." );
956 help_text = _( "Rename file" );
957 }
958 else
959 {
960 text = _( "Rename Files..." );
961 help_text = _( "Rename files" );
962 }
963
964 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_RENAME, text, help_text,
965 KiBitmap( BITMAPS::right ) );
966 }
967
968 if( can_delete )
969 {
970 if( selection.size() == 1 )
971 help_text = _( "Delete the file and its content" );
972 else
973 help_text = _( "Delete the files and their contents" );
974
975 if( can_switch_to_project
976 || can_create_new_directory
977 || can_open_this_directory
978 || can_edit
979 || can_rename )
980 {
981 popup_menu.AppendSeparator();
982 }
983
984#ifdef __WINDOWS__
985 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Delete" ), help_text,
986 KiBitmap( BITMAPS::trash ) );
987#else
988 KIUI::AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Move to Trash" ), help_text,
989 KiBitmap( BITMAPS::trash ) );
990#endif
991 }
992
993 if( vcs_menu )
994 {
995 wxMenu* vcs_submenu = new wxMenu();
996 wxMenu* branch_submenu = new wxMenu();
997 wxMenuItem* vcs_menuitem = nullptr;
998
999 vcs_menuitem = vcs_submenu->Append( ID_GIT_INITIALIZE_PROJECT,
1000 _( "Add Project to Version Control..." ),
1001 _( "Initialize a new repository" ) );
1002 vcs_menuitem->Enable( vcs_can_init );
1003
1004
1005 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_PROJECT, _( "Commit Project..." ),
1006 _( "Commit changes to the local repository" ) );
1007 vcs_menuitem->Enable( vcs_can_commit );
1008
1009 vcs_menuitem = vcs_submenu->Append( ID_GIT_PUSH, _( "Push" ),
1010 _( "Push committed local changes to remote repository" ) );
1011 vcs_menuitem->Enable( vcs_can_push );
1012
1013 vcs_menuitem = vcs_submenu->Append( ID_GIT_PULL, _( "Pull" ),
1014 _( "Pull changes from remote repository into local" ) );
1015 vcs_menuitem->Enable( vcs_can_pull );
1016
1017 vcs_submenu->AppendSeparator();
1018
1019 vcs_menuitem = vcs_submenu->Append( ID_GIT_COMMIT_FILE, _( "Commit File..." ),
1020 _( "Commit changes to the local repository" ) );
1021 vcs_menuitem->Enable( vcs_can_commit );
1022
1023 vcs_submenu->AppendSeparator();
1024
1025 // vcs_menuitem = vcs_submenu->Append( ID_GIT_COMPARE, _( "Diff" ),
1026 // _( "Show changes between the repository and working tree" ) );
1027 // vcs_menuitem->Enable( vcs_can_diff );
1028
1029 std::vector<wxString> branchNames = m_TreeProject->GitCommon()->GetBranchNames();
1030
1031 // Skip the first one (that is the current branch)
1032 for( size_t ii = 1; ii < branchNames.size() && ii < 6; ++ii )
1033 {
1034 wxString msg = _( "Switch to branch " ) + branchNames[ii];
1035 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH + ii, branchNames[ii], msg );
1036 vcs_menuitem->Enable( vcs_can_switch );
1037 }
1038
1039 vcs_menuitem = branch_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Other..." ),
1040 _( "Switch to a different branch" ) );
1041 vcs_menuitem->Enable( vcs_can_switch );
1042
1043 vcs_submenu->Append( ID_GIT_SWITCH_BRANCH, _( "Switch to Branch" ), branch_submenu );
1044
1045 vcs_submenu->AppendSeparator();
1046
1047 vcs_menuitem = vcs_submenu->Append( ID_GIT_REMOVE_VCS, _( "Remove Version Control" ),
1048 _( "Delete all version control files from the project directory." ) );
1049 vcs_menuitem->Enable( vcs_can_remove );
1050
1051 popup_menu.AppendSeparator();
1052 popup_menu.AppendSubMenu( vcs_submenu, _( "Version Control" ) );
1053 }
1054
1055 if( popup_menu.GetMenuItemCount() > 0 )
1056 PopupMenu( &popup_menu );
1057}
1058
1059
1061{
1062 wxString editorname = Pgm().GetTextEditor();
1063
1064 if( editorname.IsEmpty() )
1065 {
1066 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
1067 return;
1068 }
1069
1070 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1071
1072 for( PROJECT_TREE_ITEM* item_data : tree_data )
1073 {
1074 wxString fullFileName = item_data->GetFileName();
1075
1076 if( !fullFileName.IsEmpty() )
1077 {
1078 ExecuteFile( editorname, fullFileName.wc_str(), nullptr, false );
1079 }
1080 }
1081}
1082
1083
1084void PROJECT_TREE_PANE::onDeleteFile( wxCommandEvent& event )
1085{
1086 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1087
1088 for( PROJECT_TREE_ITEM* item_data : tree_data )
1089 item_data->Delete();
1090}
1091
1092
1093void PROJECT_TREE_PANE::onRenameFile( wxCommandEvent& event )
1094{
1095 wxTreeItemId curr_item = m_TreeProject->GetFocusedItem();
1096 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1097
1098 // XXX: Unnecessary?
1099 if( tree_data.size() != 1 )
1100 return;
1101
1102 wxString buffer = m_TreeProject->GetItemText( curr_item );
1103 wxString msg = wxString::Format( _( "Change filename: '%s'" ),
1104 tree_data[0]->GetFileName() );
1105 wxTextEntryDialog dlg( wxGetTopLevelParent( this ), msg, _( "Change filename" ), buffer );
1106
1107 if( dlg.ShowModal() != wxID_OK )
1108 return; // canceled by user
1109
1110 buffer = dlg.GetValue();
1111 buffer.Trim( true );
1112 buffer.Trim( false );
1113
1114 if( buffer.IsEmpty() )
1115 return; // empty file name not allowed
1116
1117 tree_data[0]->Rename( buffer, true );
1118 m_isRenaming = true;
1119}
1120
1121
1122void PROJECT_TREE_PANE::onSelect( wxTreeEvent& Event )
1123{
1124 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
1125
1126 if( tree_data.size() != 1 )
1127 return;
1128
1129 // Bookmark the selected item but don't try and activate it until later. If we do it now,
1130 // there will be more events at least on Windows in this frame that will steal focus from
1131 // any newly launched windows
1132 m_selectedItem = tree_data[0];
1133}
1134
1135
1136void PROJECT_TREE_PANE::onIdle( wxIdleEvent& aEvent )
1137{
1138 // Idle executes once all other events finished processing. This makes it ideal to launch
1139 // a new window without starting Focus wars.
1140 if( m_watcherNeedReset )
1141 {
1142 m_selectedItem = nullptr;
1144 }
1145
1146 if( m_selectedItem != nullptr && m_TreeProject->GetRootItem().IsOk() )
1147 {
1148 // Make sure m_selectedItem still exists in the tree before activating it.
1149 std::vector<wxTreeItemId> validItemIds;
1150 m_TreeProject->GetItemsRecursively( m_TreeProject->GetRootItem(), validItemIds );
1151
1152 for( wxTreeItemId id : validItemIds )
1153 {
1154 if( GetItemIdData( id ) == m_selectedItem )
1155 {
1156 // Activate launches a window which may run the event loop on top of us and cause
1157 // onIdle to get called again, so be sure to null out m_selectedItem first.
1159 m_selectedItem = nullptr;
1160
1161 item->Activate( this );
1162 break;
1163 }
1164 }
1165 }
1166}
1167
1168
1169void PROJECT_TREE_PANE::onExpand( wxTreeEvent& Event )
1170{
1171 wxTreeItemId itemId = Event.GetItem();
1172 PROJECT_TREE_ITEM* tree_data = GetItemIdData( itemId );
1173
1174 if( !tree_data )
1175 return;
1176
1177 if( tree_data->GetType() != TREE_FILE_TYPE::DIRECTORY )
1178 return;
1179
1180 // explore list of non populated subdirs, and populate them
1181 wxTreeItemIdValue cookie;
1182 wxTreeItemId kid = m_TreeProject->GetFirstChild( itemId, cookie );
1183
1184#ifndef __WINDOWS__
1185 bool subdir_populated = false;
1186#endif
1187
1188 for( ; kid.IsOk(); kid = m_TreeProject->GetNextChild( itemId, cookie ) )
1189 {
1190 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1191
1192 if( !itemData || itemData->GetType() != TREE_FILE_TYPE::DIRECTORY )
1193 continue;
1194
1195 if( itemData->IsPopulated() )
1196 continue;
1197
1198 wxString fileName = itemData->GetFileName();
1199 wxDir dir( fileName );
1200
1201 if( dir.IsOpened() )
1202 {
1203 std::vector<wxString> projects = getProjects( dir );
1204 wxString dir_filename;
1205 bool haveFile = dir.GetFirst( &dir_filename );
1206
1207 while( haveFile )
1208 {
1209 // Add name to tree item, but do not recurse in subdirs:
1210 wxString name = fileName + wxFileName::GetPathSeparator() + dir_filename;
1211 addItemToProjectTree( name, kid, &projects, false );
1212
1213 haveFile = dir.GetNext( &dir_filename );
1214 }
1215
1216 itemData->SetPopulated( true ); // set state to populated
1217
1218#ifndef __WINDOWS__
1219 subdir_populated = true;
1220#endif
1221 }
1222
1223 // Sort filenames by alphabetic order
1224 m_TreeProject->SortChildren( kid );
1225 }
1226
1227#ifndef __WINDOWS__
1228 if( subdir_populated )
1229 m_watcherNeedReset = true;
1230#endif
1231}
1232
1233
1234std::vector<PROJECT_TREE_ITEM*> PROJECT_TREE_PANE::GetSelectedData()
1235{
1236 wxArrayTreeItemIds selection;
1237 std::vector<PROJECT_TREE_ITEM*> data;
1238
1239 m_TreeProject->GetSelections( selection );
1240
1241 for( const wxTreeItemId itemId : selection )
1242 {
1243 PROJECT_TREE_ITEM* item = GetItemIdData( itemId );
1244
1245 if( !item )
1246 {
1247 wxLogTrace( traceGit, wxS( "Null tree item returned for selection, dynamic_cast failed?" ) );
1248 continue;
1249 }
1250
1251 data.push_back( item );
1252 }
1253
1254 return data;
1255}
1256
1257
1259{
1260 return dynamic_cast<PROJECT_TREE_ITEM*>( m_TreeProject->GetItemData( aId ) );
1261}
1262
1263
1264wxTreeItemId PROJECT_TREE_PANE::findSubdirTreeItem( const wxString& aSubDir )
1265{
1266 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1267
1268 // If the subdir is the current working directory, return m_root
1269 // in main list:
1270 if( prj_dir == aSubDir )
1271 return m_root;
1272
1273 // The subdir is in the main tree or in a subdir: Locate it
1274 wxTreeItemIdValue cookie;
1275 wxTreeItemId root_id = m_root;
1276 std::stack<wxTreeItemId> subdirs_id;
1277
1278 wxTreeItemId child = m_TreeProject->GetFirstChild( root_id, cookie );
1279
1280 while( true )
1281 {
1282 if( ! child.IsOk() )
1283 {
1284 if( subdirs_id.empty() ) // all items were explored
1285 {
1286 root_id = child; // Not found: return an invalid wxTreeItemId
1287 break;
1288 }
1289 else
1290 {
1291 root_id = subdirs_id.top();
1292 subdirs_id.pop();
1293 child = m_TreeProject->GetFirstChild( root_id, cookie );
1294
1295 if( !child.IsOk() )
1296 continue;
1297 }
1298 }
1299
1300 PROJECT_TREE_ITEM* itemData = GetItemIdData( child );
1301
1302 if( itemData && ( itemData->GetType() == TREE_FILE_TYPE::DIRECTORY ) )
1303 {
1304 if( itemData->GetFileName() == aSubDir ) // Found!
1305 {
1306 root_id = child;
1307 break;
1308 }
1309
1310 // child is a subdir, push in list to explore it later
1311 if( itemData->IsPopulated() )
1312 subdirs_id.push( child );
1313 }
1314
1315 child = m_TreeProject->GetNextChild( root_id, cookie );
1316 }
1317
1318 return root_id;
1319}
1320
1321
1322void PROJECT_TREE_PANE::onFileSystemEvent( wxFileSystemWatcherEvent& event )
1323{
1324 // No need to process events when we're shutting down
1325 if( !m_watcher )
1326 return;
1327
1328 // Ignore events that are not file creation, deletion, renaming or modification because
1329 // they are not relevant to the project tree.
1330 if( !( event.GetChangeType() & ( wxFSW_EVENT_CREATE |
1331 wxFSW_EVENT_DELETE |
1332 wxFSW_EVENT_RENAME |
1333 wxFSW_EVENT_MODIFY ) ) )
1334 {
1335 return;
1336 }
1337
1338 const wxFileName& pathModified = event.GetPath();
1339 wxString subdir = pathModified.GetPath();
1340 wxString fn = pathModified.GetFullPath();
1341
1342 // Adjust directories to look like a file item (path and name).
1343 if( pathModified.GetFullName().IsEmpty() )
1344 {
1345 subdir = subdir.BeforeLast( '/' );
1346 fn = fn.BeforeLast( '/' );
1347 }
1348
1349 wxTreeItemId root_id = findSubdirTreeItem( subdir );
1350
1351 if( !root_id.IsOk() )
1352 return;
1353
1354 CallAfter( [this] ()
1355 {
1356 wxLogTrace( traceGit, wxS( "File system event detected, updating tree cache" ) );
1357 m_gitStatusTimer.Start( 1500, wxTIMER_ONE_SHOT );
1358 } );
1359
1360 wxTreeItemIdValue cookie; // dummy variable needed by GetFirstChild()
1361 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1362
1363 switch( event.GetChangeType() )
1364 {
1365 case wxFSW_EVENT_CREATE:
1366 {
1367 wxTreeItemId newitem = addItemToProjectTree( fn, root_id, nullptr, true );
1368
1369 // If we are in the process of renaming a file, select the new one
1370 // This is needed for MSW and OSX, since we don't get RENAME events from them, just a
1371 // pair of DELETE and CREATE events.
1372 if( m_isRenaming && newitem.IsOk() )
1373 {
1374 m_TreeProject->SelectItem( newitem );
1375 m_isRenaming = false;
1376 }
1377 }
1378 break;
1379
1380 case wxFSW_EVENT_DELETE:
1381 while( kid.IsOk() )
1382 {
1383 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1384
1385 if( itemData && itemData->GetFileName() == fn )
1386 {
1387 m_TreeProject->Delete( kid );
1388 return;
1389 }
1390 kid = m_TreeProject->GetNextChild( root_id, cookie );
1391 }
1392 break;
1393
1394 case wxFSW_EVENT_RENAME :
1395 {
1396 const wxFileName& newpath = event.GetNewPath();
1397 wxString newdir = newpath.GetPath();
1398 wxString newfn = newpath.GetFullPath();
1399
1400 while( kid.IsOk() )
1401 {
1402 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1403
1404 if( itemData && itemData->GetFileName() == fn )
1405 {
1406 m_TreeProject->Delete( kid );
1407 break;
1408 }
1409
1410 kid = m_TreeProject->GetNextChild( root_id, cookie );
1411 }
1412
1413 // Add the new item only if it is not the current project file (root item).
1414 // Remember: this code is called by a wxFileSystemWatcherEvent event, and not always
1415 // called after an actual file rename, and the cleanup code does not explore the
1416 // root item, because it cannot be renamed by the user. Also, ensure the new file
1417 // actually exists on the file system before it is readded. On Linux, moving a file
1418 // to the trash can cause the same path to be returned in both the old and new paths
1419 // of the event, even though the file isn't there anymore.
1420 PROJECT_TREE_ITEM* rootData = GetItemIdData( root_id );
1421
1422 if( rootData && newpath.Exists() && ( newfn != rootData->GetFileName() ) )
1423 {
1424 wxTreeItemId newroot_id = findSubdirTreeItem( newdir );
1425 wxTreeItemId newitem = addItemToProjectTree( newfn, newroot_id, nullptr, true );
1426
1427 // If the item exists, select it
1428 if( newitem.IsOk() )
1429 m_TreeProject->SelectItem( newitem );
1430 }
1431
1432 m_isRenaming = false;
1433 }
1434 break;
1435
1436 default:
1437 return;
1438 }
1439
1440 // Sort filenames by alphabetic order
1441 m_TreeProject->SortChildren( root_id );
1442}
1443
1444
1446{
1447 m_watcherNeedReset = false;
1448
1449 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1450
1451#if defined( _WIN32 )
1452 KISTATUSBAR* statusBar = static_cast<KISTATUSBAR*>( m_Parent->GetStatusBar() );
1453
1454 if( KIPLATFORM::ENV::IsNetworkPath( prj_dir ) )
1455 {
1456 // Due to a combination of a bug in SAMBA sending bad change event IDs and wxWidgets
1457 // choosing to fault on an invalid event ID instead of sanely ignoring them we need to
1458 // avoid spawning a filewatcher. Unfortunately this punishes corporate environments with
1459 // Windows Server shares :/
1460 m_Parent->m_FileWatcherInfo = _( "Network path: not monitoring folder changes" );
1462 return;
1463 }
1464 else
1465 {
1466 m_Parent->m_FileWatcherInfo = _( "Local path: monitoring folder changes" );
1468 }
1469#endif
1470
1471 // Prepare file watcher:
1472 if( m_watcher )
1473 {
1474 m_watcher->RemoveAll();
1475 }
1476 else
1477 {
1479 m_watcher->SetOwner( this );
1480 }
1481
1482 // We can see wxString under a debugger, not a wxFileName
1483 wxFileName fn;
1484 fn.AssignDir( prj_dir );
1485 fn.DontFollowLink();
1486
1487 // Add directories which should be monitored.
1488 // under windows, we add the curr dir and all subdirs
1489 // under unix, we add only the curr dir and the populated subdirs
1490 // see http://docs.wxwidgets.org/trunk/classwx_file_system_watcher.htm
1491 // under unix, the file watcher needs more work to be efficient
1492 // moreover, under wxWidgets 2.9.4, AddTree does not work properly.
1493 {
1494 wxLogNull logNo; // avoid log messages
1495#ifdef __WINDOWS__
1496 if( ! m_watcher->AddTree( fn ) )
1497 {
1498 wxLogTrace( tracePathsAndFiles, "%s: failed to add '%s'\n", __func__,
1499 TO_UTF8( fn.GetFullPath() ) );
1500 return;
1501 }
1502 }
1503#else
1504 if( !m_watcher->Add( fn ) )
1505 {
1506 wxLogTrace( tracePathsAndFiles, "%s: failed to add '%s'\n", __func__,
1507 TO_UTF8( fn.GetFullPath() ) );
1508 return;
1509 }
1510 }
1511
1512 if( m_TreeProject->IsEmpty() )
1513 return;
1514
1515 // Add subdirs
1516 wxTreeItemIdValue cookie;
1517 wxTreeItemId root_id = m_root;
1518
1519 std::stack < wxTreeItemId > subdirs_id;
1520
1521 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1522 int total_watch_count = 0;
1523
1524 while( total_watch_count < ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
1525 {
1526 if( !kid.IsOk() )
1527 {
1528 if( subdirs_id.empty() ) // all items were explored
1529 {
1530 break;
1531 }
1532 else
1533 {
1534 root_id = subdirs_id.top();
1535 subdirs_id.pop();
1536 kid = m_TreeProject->GetFirstChild( root_id, cookie );
1537
1538 if( !kid.IsOk() )
1539 continue;
1540 }
1541 }
1542
1543 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1544
1545 if( itemData && itemData->GetType() == TREE_FILE_TYPE::DIRECTORY )
1546 {
1547 // we can see wxString under a debugger, not a wxFileName
1548 const wxString& path = itemData->GetFileName();
1549
1550 wxLogTrace( tracePathsAndFiles, "%s: add '%s'\n", __func__, TO_UTF8( path ) );
1551
1552 if( wxFileName::IsDirReadable( path ) ) // linux whines about watching protected dir
1553 {
1554 {
1555 // Silence OS errors that come from the watcher
1556 wxLogNull silence;
1557 fn.AssignDir( path );
1558 m_watcher->Add( fn );
1559 total_watch_count++;
1560 }
1561
1562 // if kid is a subdir, push in list to explore it later
1563 if( itemData->IsPopulated() && m_TreeProject->GetChildrenCount( kid ) )
1564 subdirs_id.push( kid );
1565 }
1566 }
1567
1568 kid = m_TreeProject->GetNextChild( root_id, cookie );
1569 }
1570
1571 if( total_watch_count >= ADVANCED_CFG::GetCfg().m_MaxFilesystemWatchers )
1572 wxLogTrace( tracePathsAndFiles, "%s: too many directories to watch\n", __func__ );
1573#endif
1574
1575#if defined(DEBUG) && 1
1576 wxArrayString paths;
1577 m_watcher->GetWatchedPaths( &paths );
1578 wxLogTrace( tracePathsAndFiles, "%s: watched paths:", __func__ );
1579
1580 for( unsigned ii = 0; ii < paths.GetCount(); ii++ )
1581 wxLogTrace( tracePathsAndFiles, " %s\n", TO_UTF8( paths[ii] ) );
1582#endif
1583 }
1584
1585
1587{
1588 // Make sure we don't try to inspect the tree after we've deleted its items.
1590
1591 m_TreeProject->DeleteAllItems();
1592
1593 // Remove the git repository when the project is unloaded
1594 if( m_TreeProject->GetGitRepo() )
1595 {
1597
1598 // We need to lock the mutex to ensure that no other thread is using the git repository
1599 std::unique_lock<std::mutex> lock( m_TreeProject->GitCommon()->m_gitActionMutex, std::try_to_lock );
1600
1601 if( !lock.owns_lock() )
1602 {
1603 // Block until any in-flight Git actions complete, showing a pulsing progress dialog
1604 {
1605 wxProgressDialog progress( _( "Please wait" ), _( "Waiting for Git operations to finish..." ),
1606 100, this, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_SMOOTH );
1607
1608 // Keep trying to acquire the lock, pulsing the dialog every 100 ms
1609 while ( !lock.try_lock() )
1610 {
1611 progress.Pulse();
1612 std::this_thread::sleep_for( std::chrono::milliseconds(100) );
1613 // allow UI events to process so dialog remains responsive
1614 wxYield();
1615 }
1616 }
1617 }
1618
1619 git_repository* repo = m_TreeProject->GetGitRepo();
1621 m_TreeProject->SetGitRepo( nullptr );
1622 }
1623}
1624
1625
1626void PROJECT_TREE_PANE::onThemeChanged( wxSysColourChangedEvent &aEvent )
1627{
1630 m_TreeProject->Refresh();
1631
1632 aEvent.Skip();
1633}
1634
1635
1636void PROJECT_TREE_PANE::onPaint( wxPaintEvent& event )
1637{
1638 wxRect rect( wxPoint( 0, 0 ), GetClientSize() );
1639 wxPaintDC dc( this );
1640
1641 dc.SetBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_FRAMEBK ) );
1642 dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_ACTIVEBORDER ), 1 ) );
1643
1644 dc.DrawLine( rect.GetLeft(), rect.GetTop(), rect.GetLeft(), rect.GetBottom() );
1645 dc.DrawLine( rect.GetRight(), rect.GetTop(), rect.GetRight(), rect.GetBottom() );
1646}
1647
1648
1649void KICAD_MANAGER_FRAME::OnChangeWatchedPaths( wxCommandEvent& aEvent )
1650{
1652}
1653
1654
1655void PROJECT_TREE_PANE::onGitInitializeProject( wxCommandEvent& aEvent )
1656{
1657 PROJECT_TREE_ITEM* tree_data = GetItemIdData( m_TreeProject->GetRootItem() );
1658
1659 wxString dir = tree_data->GetDir();
1660
1661 if( dir.empty() )
1662 {
1663 wxLogError( "Failed to initialize git project: project directory is empty." );
1664 return;
1665 }
1666
1667 GIT_INIT_HANDLER initHandler( m_TreeProject->GitCommon() );
1668 wxWindow* topLevelParent = wxGetTopLevelParent( this );
1669
1670 if( initHandler.IsRepository( dir ) )
1671 {
1672 DisplayInfoMessage( topLevelParent,
1673 _( "The selected directory is already a Git project." ) );
1674 return;
1675 }
1676
1677 InitResult result = initHandler.InitializeRepository( dir );
1678 if( result != InitResult::Success )
1679 {
1680 DisplayErrorMessage( m_parent, _( "Failed to initialize Git project." ),
1681 initHandler.GetErrorString() );
1682 return;
1683 }
1684
1685 m_gitLastError = GIT_ERROR_NONE;
1686
1687 DIALOG_GIT_REPOSITORY dlg( topLevelParent, initHandler.GetRepo() );
1688 dlg.SetTitle( _( "Set default remote" ) );
1689 dlg.SetSkipButtonLabel( _( "Skip" ) );
1690
1691 if( dlg.ShowModal() != wxID_OK )
1692 return;
1693
1694 // Set up the remote
1695 RemoteConfig remoteConfig;
1696 remoteConfig.url = dlg.GetRepoURL();
1697 remoteConfig.username = dlg.GetUsername();
1698 remoteConfig.password = dlg.GetPassword();
1699 remoteConfig.sshKey = dlg.GetRepoSSHPath();
1700 remoteConfig.connType = dlg.GetRepoType();
1701
1702 if( !initHandler.SetupRemote( remoteConfig ) )
1703 {
1704 DisplayErrorMessage( m_parent, _( "Failed to set default remote." ),
1705 initHandler.GetErrorString() );
1706 return;
1707 }
1708
1709 m_gitLastError = GIT_ERROR_NONE;
1710
1712 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetch Remote" ), 1, PR_NO_ABORT ) );
1713 handler.PerformFetch();
1714
1718
1722 Prj().GetLocalSettings().m_GitRepoType = "https";
1723 else
1724 Prj().GetLocalSettings().m_GitRepoType = "local";
1725}
1726
1727
1728void PROJECT_TREE_PANE::onGitCompare( wxCommandEvent& aEvent )
1729{
1730
1731}
1732
1733
1734void PROJECT_TREE_PANE::onGitPullProject( wxCommandEvent& aEvent )
1735{
1736 git_repository* repo = m_TreeProject->GetGitRepo();
1737
1738 if( !repo )
1739 return;
1740
1742
1743 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetch Remote" ), 1,
1744 PR_NO_ABORT ) );
1745
1746 if( handler.PerformPull() < PullResult::Success )
1747 {
1748 wxString errorMessage = handler.GetErrorString();
1749
1750 DisplayErrorMessage( m_parent, _( "Failed to pull project" ), errorMessage );
1751 }
1752
1753 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1754}
1755
1756
1757void PROJECT_TREE_PANE::onGitPushProject( wxCommandEvent& aEvent )
1758{
1759 git_repository* repo = m_TreeProject->GetGitRepo();
1760
1761 if( !repo )
1762 return;
1763
1765
1766 handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetch Remote" ), 1,
1767 PR_NO_ABORT ) );
1768
1769 if( handler.PerformPush() != PushResult::Success )
1770 {
1771 wxString errorMessage = handler.GetErrorString();
1772
1773 DisplayErrorMessage( m_parent, _( "Failed to push project" ), errorMessage );
1774 }
1775
1776 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1777}
1778
1779
1780void PROJECT_TREE_PANE::onGitSwitchBranch( wxCommandEvent& aEvent )
1781{
1782 if( !m_TreeProject->GetGitRepo() )
1783 return;
1784
1785 GIT_BRANCH_HANDLER branchHandler( m_TreeProject->GitCommon() );
1786 wxString branchName;
1787
1788 if( aEvent.GetId() == ID_GIT_SWITCH_BRANCH )
1789 {
1790 DIALOG_GIT_SWITCH dlg( wxGetTopLevelParent( this ), m_TreeProject->GetGitRepo() );
1791
1792 int retval = dlg.ShowModal();
1793 branchName = dlg.GetBranchName();
1794
1795 if( retval == wxID_ADD )
1797 else if( retval != wxID_OK )
1798 return;
1799 }
1800 else
1801 {
1802 std::vector<wxString> branches = m_TreeProject->GitCommon()->GetBranchNames();
1803 int branchIndex = aEvent.GetId() - ID_GIT_SWITCH_BRANCH;
1804
1805 if( branchIndex < 0 || static_cast<size_t>( branchIndex ) >= branches.size() )
1806 return;
1807
1808 branchName = branches[branchIndex];
1809 }
1810
1811 wxLogTrace( traceGit, wxS( "onGitSwitchBranch: Switching to branch '%s'" ), branchName );
1812 if( branchHandler.SwitchToBranch( branchName ) != BranchResult::Success )
1813 {
1814 DisplayError( m_parent, branchHandler.GetErrorString() );
1815 }
1816}
1817
1818
1819void PROJECT_TREE_PANE::onGitRemoveVCS( wxCommandEvent& aEvent )
1820{
1821 git_repository* repo = m_TreeProject->GetGitRepo();
1822
1823 if( !repo
1824 || !IsOK( wxGetTopLevelParent( this ),
1825 _( "Are you sure you want to remove Git tracking from this project?" ) ) )
1826 {
1827 return;
1828 }
1829
1830 // Fully delete the git repository on disk
1831 wxLogTrace( traceGit, wxS( "onGitRemoveVCS: Removing VCS from project" ) );
1832 wxString errors;
1833
1834 if( !KIGIT::PROJECT_GIT_UTILS::RemoveVCS( repo, Prj().GetProjectPath(), true, &errors ) )
1835 {
1836 DisplayErrorMessage( m_parent, _( "Failed to remove VCS" ), errors );
1837 return;
1838 }
1839
1840 m_TreeProject->SetGitRepo( nullptr );
1841
1842 // Clear all item states
1843 std::stack<wxTreeItemId> items;
1844 items.push( m_TreeProject->GetRootItem() );
1845
1846 while( !items.empty() )
1847 {
1848 wxTreeItemId current = items.top();
1849 items.pop();
1850
1851 // Process the current item
1852 m_TreeProject->SetItemState( current, wxTREE_ITEMSTATE_NONE );
1853
1854 wxTreeItemIdValue cookie;
1855 wxTreeItemId child = m_TreeProject->GetFirstChild( current, cookie );
1856
1857 while( child.IsOk() )
1858 {
1859 items.push( child );
1860 child = m_TreeProject->GetNextChild( current, cookie );
1861 }
1862 }
1863}
1864
1865
1867{
1868 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Updating git status icons" ) );
1869 std::unique_lock<std::mutex> lock( m_gitStatusMutex, std::try_to_lock );
1870
1871 if( !lock.owns_lock() )
1872 {
1873 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Failed to acquire lock for git status icon update" ) );
1874 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
1875 return;
1876 }
1877
1878 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
1879 {
1880 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Git is disabled or tree control is null" ) );
1881 return;
1882 }
1883
1884 std::stack<wxTreeItemId> items;
1885 items.push(m_TreeProject->GetRootItem());
1886
1887 while( !items.empty() )
1888 {
1889 wxTreeItemId current = items.top();
1890 items.pop();
1891
1892 if( m_TreeProject->ItemHasChildren( current ) )
1893 {
1894 wxTreeItemIdValue cookie;
1895 wxTreeItemId child = m_TreeProject->GetFirstChild( current, cookie );
1896
1897 while( child.IsOk() )
1898 {
1899 items.push( child );
1900
1901 if( auto it = m_gitStatusIcons.find( child ); it != m_gitStatusIcons.end() )
1902 {
1903 m_TreeProject->SetItemState( child, static_cast<int>( it->second ) );
1904 }
1905
1906 child = m_TreeProject->GetNextChild( current, cookie );
1907 }
1908 }
1909 }
1910
1911 if (!m_gitCurrentBranchName.empty())
1912 {
1913 wxTreeItemId kid = m_TreeProject->GetRootItem();
1914 PROJECT_TREE_ITEM* rootItem = GetItemIdData( kid );
1915 wxString filename = wxFileNameFromPath( rootItem->GetFileName() );
1916 m_TreeProject->SetItemText( kid, filename + " [" + m_gitCurrentBranchName + "]" );
1917 m_gitIconsInitialized = true;
1918 }
1919
1920 wxLogTrace( traceGit, wxS( "updateGitStatusIcons: Git status icons updated" ) );
1921}
1922
1923
1925{
1926 wxLogTrace( traceGit, wxS( "updateTreeCache: Updating tree cache" ) );
1927
1928 std::unique_lock<std::mutex> lock( m_gitTreeCacheMutex, std::try_to_lock );
1929
1930 if( !lock.owns_lock() )
1931 {
1932 wxLogTrace( traceGit, wxS( "updateTreeCache: Failed to acquire lock for tree cache update" ) );
1933 return;
1934 }
1935
1936 if( !m_TreeProject )
1937 {
1938 wxLogTrace( traceGit, wxS( "updateTreeCache: Tree control is null" ) );
1939 return;
1940 }
1941
1942 wxTreeItemId kid = m_TreeProject->GetRootItem();
1943
1944 if( !kid.IsOk() )
1945 return;
1946
1947 // Collect a map to easily set the state of each item
1948 m_gitTreeCache.clear();
1949 std::stack<wxTreeItemId> items;
1950 items.push( kid );
1951
1952 while( !items.empty() )
1953 {
1954 kid = items.top();
1955 items.pop();
1956
1957 PROJECT_TREE_ITEM* nextItem = GetItemIdData( kid );
1958
1959 wxString gitAbsPath = nextItem->GetFileName();
1960#ifdef _WIN32
1961 gitAbsPath.Replace( wxS( "\\" ), wxS( "/" ) );
1962#endif
1963 m_gitTreeCache[gitAbsPath] = kid;
1964
1965 wxTreeItemIdValue cookie;
1966 wxTreeItemId child = m_TreeProject->GetFirstChild( kid, cookie );
1967
1968 while( child.IsOk() )
1969 {
1970 items.push( child );
1971 child = m_TreeProject->GetNextChild( kid, cookie );
1972 }
1973 }
1974}
1975
1976
1978{
1979 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Updating git status icons" ) );
1980#if defined( _WIN32 )
1982
1983 if( refresh != 0
1985 {
1986 // Need to treat windows network paths special here until we get the samba bug fixed
1987 // https://github.com/wxWidgets/wxWidgets/issues/18953
1988 CallAfter(
1989 [this, refresh]()
1990 {
1991 m_gitStatusTimer.Start( refresh, wxTIMER_ONE_SHOT );
1992 } );
1993 }
1994
1995#endif
1996
1997 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
1998 return;
1999
2000 std::unique_lock<std::mutex> lock1( m_gitStatusMutex, std::try_to_lock );
2001 std::unique_lock<std::mutex> lock2( m_gitTreeCacheMutex, std::try_to_lock );
2002
2003 if( !lock1.owns_lock() || !lock2.owns_lock() )
2004 {
2005 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Failed to acquire locks for git status icon update" ) );
2006 return;
2007 }
2008
2009 if( !m_TreeProject->GetGitRepo() )
2010 {
2011 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: No git repository found" ) );
2012 return;
2013 }
2014
2015 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
2016
2017 // Set up pathspec for project files
2018 wxFileName rootFilename( Prj().GetProjectFullName() );
2019 wxString repoWorkDir = statusHandler.GetWorkingDirectory();
2020
2021 wxFileName relative = rootFilename;
2022 relative.MakeRelativeTo( repoWorkDir );
2023 wxString pathspecStr = relative.GetPath( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR );
2024
2025#ifdef _WIN32
2026 pathspecStr.Replace( wxS( "\\" ), wxS( "/" ) );
2027#endif
2028
2029 // Get file status
2030 auto fileStatusMap = statusHandler.GetFileStatus( pathspecStr );
2031 auto [localChanges, remoteChanges] = m_TreeProject->GitCommon()->GetDifferentFiles();
2032 statusHandler.UpdateRemoteStatus( localChanges, remoteChanges, fileStatusMap );
2033
2034 bool updated = false;
2035
2036 // Update status icons based on file status
2037 for( const auto& [absPath, fileStatus] : fileStatusMap )
2038 {
2039 auto iter = m_gitTreeCache.find( absPath );
2040 if( iter == m_gitTreeCache.end() )
2041 {
2042 wxLogTrace( traceGit, wxS( "File '%s' not found in tree cache" ), absPath );
2043 continue;
2044 }
2045
2046 auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second, fileStatus.status );
2047 if( inserted || it->second != fileStatus.status )
2048 updated = true;
2049 it->second = fileStatus.status;
2050 }
2051
2052 // Get the current branch name
2054
2055 wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Updated git status icons" ) );
2056
2057 // Update UI if icons changed
2058 if( updated || !m_gitIconsInitialized )
2059 {
2060 CallAfter(
2061 [this]()
2062 {
2064 } );
2065 }
2066}
2067
2068
2069void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
2070{
2071 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
2072
2073 git_repository* repo = m_TreeProject->GetGitRepo();
2074
2075 if( repo == nullptr )
2076 {
2077 wxMessageBox( _( "The selected directory is not a Git project." ) );
2078 return;
2079 }
2080
2081 // Get git configuration
2082 GIT_CONFIG_HANDLER configHandler( m_TreeProject->GitCommon() );
2083 GitUserConfig userConfig = configHandler.GetUserConfig();
2084
2085 // Collect modified files in the repository
2086 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
2087 auto fileStatusMap = statusHandler.GetFileStatus();
2088
2089 std::map<wxString, int> modifiedFiles;
2090 std::set<wxString> selected_files;
2091
2092 for( PROJECT_TREE_ITEM* item : tree_data )
2093 {
2094 if( item->GetType() != TREE_FILE_TYPE::DIRECTORY )
2095 selected_files.emplace( item->GetFileName() );
2096 }
2097
2098 wxString repoWorkDir = statusHandler.GetWorkingDirectory();
2099
2100 for( const auto& [absPath, fileStatus] : fileStatusMap )
2101 {
2102 // Skip current, conflicted, or ignored files
2103 if( fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT
2105 || fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_IGNORED )
2106 {
2107 continue;
2108 }
2109
2110 wxFileName fn( absPath );
2111
2112 // Convert to relative path for the modifiedFiles map
2113 wxString relativePath = absPath;
2114 if( relativePath.StartsWith( repoWorkDir ) )
2115 {
2116 relativePath = relativePath.Mid( repoWorkDir.length() );
2117#ifdef _WIN32
2118 relativePath.Replace( wxS( "\\" ), wxS( "/" ) );
2119#endif
2120 }
2121
2122 // Do not commit files outside the project directory
2123 wxString projectPath = Prj().GetProjectPath();
2124 if( !absPath.StartsWith( projectPath ) )
2125 continue;
2126
2127 // Skip lock files
2128 if( fn.GetExt().CmpNoCase( FILEEXT::LockFileExtension ) == 0 )
2129 continue;
2130
2131 // Skip autosave, lock, and backup files
2132 if( fn.GetName().StartsWith( FILEEXT::AutoSaveFilePrefix )
2133 || fn.GetName().StartsWith( FILEEXT::LockFilePrefix )
2134 || fn.GetName().EndsWith( FILEEXT::BackupFileSuffix ) )
2135 {
2136 continue;
2137 }
2138
2139 // Skip archived project backups
2140 if( fn.GetPath().Contains( Prj().GetProjectName() + wxT( "-backups" ) ) )
2141 continue;
2142
2143 if( aEvent.GetId() == ID_GIT_COMMIT_PROJECT )
2144 {
2145 modifiedFiles.emplace( relativePath, fileStatus.gitStatus );
2146 }
2147 else if( selected_files.count( absPath ) )
2148 {
2149 modifiedFiles.emplace( relativePath, fileStatus.gitStatus );
2150 }
2151 }
2152
2153 // Create a commit dialog
2154 DIALOG_GIT_COMMIT dlg( wxGetTopLevelParent( this ), repo, userConfig.authorName, userConfig.authorEmail,
2155 modifiedFiles );
2156 auto ret = dlg.ShowModal();
2157
2158 if( ret != wxID_OK )
2159 return;
2160
2161 std::vector<wxString> files = dlg.GetSelectedFiles();
2162
2163 if( dlg.GetCommitMessage().IsEmpty() )
2164 {
2165 wxMessageBox( _( "Discarding commit due to empty commit message." ) );
2166 return;
2167 }
2168
2169 if( files.empty() )
2170 {
2171 wxMessageBox( _( "Discarding commit due to empty file selection." ) );
2172 return;
2173 }
2174
2175 GIT_COMMIT_HANDLER commitHandler( repo );
2176 auto result = commitHandler.PerformCommit( files, dlg.GetCommitMessage(),
2177 dlg.GetAuthorName(), dlg.GetAuthorEmail() );
2178
2180 {
2181 wxMessageBox( wxString::Format( _( "Failed to create commit: %s" ),
2182 commitHandler.GetErrorString() ) );
2183 return;
2184 }
2185
2186 wxLogTrace( traceGit, wxS( "Created commit" ) );
2187 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
2188}
2189
2190
2191void PROJECT_TREE_PANE::onGitAddToIndex( wxCommandEvent& aEvent )
2192{
2193
2194}
2195
2196
2197bool PROJECT_TREE_PANE::canFileBeAddedToVCS( const wxString& aFile )
2198{
2199 if( !m_TreeProject->GetGitRepo() )
2200 return false;
2201
2202 GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
2203 auto fileStatusMap = statusHandler.GetFileStatus();
2204
2205 // Check if file is already tracked or staged
2206 for( const auto& [filePath, fileStatus] : fileStatusMap )
2207 {
2208 if( filePath.EndsWith( aFile ) || filePath == aFile )
2209 {
2210 // File can be added if it's untracked
2211 return fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_UNTRACKED;
2212 }
2213 }
2214
2215 // If file not found in status, it might be addable
2216 return true;
2217}
2218
2219
2220void PROJECT_TREE_PANE::onGitSyncProject( wxCommandEvent& aEvent )
2221{
2222 wxLogTrace( traceGit, "Syncing project" );
2223 git_repository* repo = m_TreeProject->GetGitRepo();
2224
2225 if( !repo )
2226 {
2227 wxLogTrace( traceGit, "sync: No git repository found" );
2228 return;
2229 }
2230
2231 GIT_SYNC_HANDLER handler( repo );
2232 handler.PerformSync();
2233}
2234
2235
2236void PROJECT_TREE_PANE::onGitFetch( wxCommandEvent& aEvent )
2237{
2238 KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon();
2239
2240 if( !gitCommon )
2241 return;
2242
2243 GIT_PULL_HANDLER handler( gitCommon );
2244 handler.PerformFetch();
2245
2246 m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
2247}
2248
2249
2250void PROJECT_TREE_PANE::onGitResolveConflict( wxCommandEvent& aEvent )
2251{
2252 git_repository* repo = m_TreeProject->GetGitRepo();
2253
2254 if( !repo )
2255 return;
2256
2257 GIT_RESOLVE_CONFLICT_HANDLER handler( repo );
2258 handler.PerformResolveConflict();
2259}
2260
2261
2262void PROJECT_TREE_PANE::onGitRevertLocal( wxCommandEvent& aEvent )
2263{
2264 git_repository* repo = m_TreeProject->GetGitRepo();
2265
2266 if( !repo )
2267 return;
2268
2269 GIT_REVERT_HANDLER handler( repo );
2270 handler.PerformRevert();
2271}
2272
2273
2274void PROJECT_TREE_PANE::onGitRemoveFromIndex( wxCommandEvent& aEvent )
2275{
2276 git_repository* repo = m_TreeProject->GetGitRepo();
2277
2278 if( !repo )
2279 return;
2280
2281 GIT_REMOVE_FROM_INDEX_HANDLER handler( repo );
2282 handler.PerformRemoveFromIndex();
2283}
2284
2285
2287{
2288
2289}
2290
2291
2292void PROJECT_TREE_PANE::onGitSyncTimer( wxTimerEvent& aEvent )
2293{
2294 wxLogTrace( traceGit, "onGitSyncTimer" );
2295 COMMON_SETTINGS::GIT& gitSettings = Pgm().GetCommonSettings()->m_Git;
2296
2297 if( !gitSettings.enableGit || !m_TreeProject )
2298 return;
2299
2301
2302 tp.push_task(
2303 [this]()
2304 {
2305 KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon();
2306
2307 if( !gitCommon )
2308 {
2309 wxLogTrace( traceGit, "onGitSyncTimer: No git repository found" );
2310 return;
2311 }
2312
2313 GIT_PULL_HANDLER handler( gitCommon );
2314 handler.PerformFetch();
2315
2316 CallAfter( [this]()
2317 {
2319 } );
2320 } );
2321
2322 if( gitSettings.updatInterval > 0 )
2323 {
2324 wxLogTrace( traceGit, "onGitSyncTimer: Restarting git sync timer" );
2325 // We store the timer interval in minutes but wxTimer uses milliseconds
2326 m_gitSyncTimer.Start( gitSettings.updatInterval * 60 * 1000, wxTIMER_ONE_SHOT );
2327 }
2328}
2329
2330
2332{
2335
2336 tp.push_task(
2337 [this]()
2338 {
2340 } );
2341}
2342
2343void PROJECT_TREE_PANE::onGitStatusTimer( wxTimerEvent& aEvent )
2344{
2345 wxLogTrace( traceGit, "onGitStatusTimer" );
2346
2347 if( !Pgm().GetCommonSettings()->m_Git.enableGit || !m_TreeProject )
2348 return;
2349
2351}
const char * name
Definition: DXF_plotter.cpp:62
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 GetRepoSSHPath() const
wxString GetUsername() const
wxString GetPassword() const
void SetSkipButtonLabel(const wxString &aLabel)
wxString GetBranchName() const
int ShowModal() override
BranchResult SwitchToBranch(const wxString &aBranchName)
Switch to the specified branch.
CommitResult PerformCommit(const std::vector< wxString > &aFiles, const wxString &aMessage, const wxString &aAuthorName, const wxString &aAuthorEmail)
wxString GetErrorString() const
GitUserConfig GetUserConfig()
Get user configuration (name and email) from git config Falls back to common settings if not found in...
bool IsRepository(const wxString &aPath)
Check if a directory is already a git repository.
InitResult InitializeRepository(const wxString &aPath)
Initialize a new git repository in the specified directory.
bool SetupRemote(const RemoteConfig &aConfig)
Set up a remote for the repository.
void SetProgressReporter(std::unique_ptr< WX_PROGRESS_REPORTER > aProgressReporter)
Definition: git_progress.h:40
bool PerformFetch(bool aSkipLock=false)
PullResult PerformPull()
PushResult PerformPush()
wxString GetCurrentBranchName()
Get the current branch name.
void UpdateRemoteStatus(const std::set< wxString > &aLocalChanges, const std::set< wxString > &aRemoteChanges, std::map< wxString, FileStatus > &aFileStatus)
Get status for modified files based on local/remote changes.
wxString GetWorkingDirectory()
Get the repository working directory path.
bool HasChangedFiles()
Check if the repository has any changed files.
std::map< wxString, FileStatus > GetFileStatus(const wxString &aPathspec=wxEmptyString)
Get detailed file status for all files in the specified path.
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)
static git_repository * GetRepositoryForFile(const char *aFilename)
Discover and open the repository that contains the given file.
static bool RemoveVCS(git_repository *&aRepo, const wxString &aProjectPath=wxEmptyString, bool aRemoveGitDir=false, wxString *aErrors=nullptr)
Remove version control from a directory by freeing the repository and optionally removing the ....
static int CreateBranch(git_repository *aRepo, const wxString &aBranchName)
Create a new branch based on HEAD.
std::mutex m_gitActionMutex
std::vector< wxString > GetBranchNames() const
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 SetCancelled(bool aCancel)
wxString GetErrorString()
git_repository * GetRepo() const
Get a pointer to the git repository.
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:136
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition: pgm_base.cpp:565
virtual const wxString & GetTextEditor(bool aCanShowFileChooser=true)
Return the path to the preferred text editor application.
Definition: pgm_base.cpp:197
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
std::unordered_map< wxString, wxTreeItemId > m_gitTreeCache
void onGitFetch(wxCommandEvent &event)
Fetch the latest changes from the git repository.
std::map< wxTreeItemId, KIGIT_COMMON::GIT_STATUS > m_gitStatusIcons
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 ...
wxString m_gitCurrentBranchName
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
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 onGitSyncTimer(wxTimerEvent &event)
void onIdle(wxIdleEvent &aEvent)
Idle event handler, used process the selected items at a point in time when all other events have bee...
void updateTreeCache()
Updates the map of the wxtreeitemid to the name of each file for use in the thread.
std::mutex m_gitStatusMutex
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 onGitStatusTimer(wxTimerEvent &event)
void shutdownFileWatcher()
Shutdown the file watcher.
std::mutex m_gitTreeCacheMutex
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.
void updateGitStatusIconMap()
This is a threaded call that will change the map of git status icons for use in the main thread.
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:63
void GetItemsRecursively(const wxTreeItemId &aParentId, std::vector< wxTreeItemId > &aItems)
KIGIT_COMMON * GitCommon() const
Definition: project_tree.h:65
void LoadIcons()
void SetGitRepo(git_repository *aRepo)
Definition: project_tree.h:62
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition: project.cpp:149
virtual PROJECT_LOCAL_SETTINGS & GetLocalSettings() const
Definition: project.h:210
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition: confirm.cpp:251
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:194
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:169
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
InitResult
int m_GitIconRefreshInterval
The interval in milliseconds to refresh the git icons in the project tree.
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:608
@ ID_PROJECT_TREE
Definition: kicad_id.h:36
@ ID_LEFT_FRAME
Definition: kicad_id.h:35
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:382
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:902
#define NAMELESS_PROJECT
default name for nameless projects
Definition: project.h:43
project_tree_ids
The frame that shows the tree list of files and subdirectories inside the working directory.
@ ID_PROJECT_OPEN_DIR
@ ID_GIT_PUSH
@ ID_GIT_REVERT_LOCAL
@ ID_JOBS_RUN
@ ID_PROJECT_SWITCH_TO_OTHER
@ ID_PROJECT_NEWDIR
@ ID_PROJECT_RENAME
@ ID_GIT_SWITCH_QUICK5
@ ID_GIT_REMOVE_VCS
@ ID_GIT_COMMIT_FILE
@ ID_GIT_PULL
@ ID_GIT_COMPARE
@ ID_PROJECT_TXTEDIT
@ ID_GIT_SWITCH_QUICK2
@ ID_GIT_REMOVE_FROM_INDEX
@ ID_GIT_RESOLVE_CONFLICT
@ ID_GIT_CLONE_PROJECT
@ ID_GIT_SWITCH_QUICK4
@ ID_GIT_SWITCH_QUICK3
@ ID_GIT_COMMIT_PROJECT
@ ID_GIT_SWITCH_BRANCH
@ ID_GIT_ADD_TO_INDEX
@ ID_GIT_SYNC_PROJECT
@ ID_GIT_FETCH
@ ID_GIT_SWITCH_QUICK1
@ ID_GIT_INITIALIZE_PROJECT
@ ID_PROJECT_DELETE
std::vector< wxString > getProjects(const wxDir &dir)
static const wxChar * s_allowedExtensionsToList[]
wxDECLARE_EVENT(UPDATE_ICONS, wxCommandEvent)
#define wxFileSystemWatcher
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: string_utils.h:429
KIGIT_COMMON::GIT_CONN_TYPE connType
wxString username
wxString password
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
Definition: thread_pool.cpp:30
static thread_pool * tp
Definition: thread_pool.cpp:28
BS::thread_pool thread_pool
Definition: thread_pool.h:31
wxLogTrace helper definitions.
TREE_FILE_TYPE
Definition of file extensions used in Kicad.
#define PR_NO_ABORT