KiCad PCB EDA Suite
project_tree_pane.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2012 SoftPLC Corporation, Dick Hollenbeck <[email protected]>
5 * Copyright (C) 2012 Jean-Pierre Charras, jp.charras at wanadoo.fr
6 * Copyright (C) 1992-2021 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
28#include <wx/regex.h>
29#include <wx/stdpaths.h>
30#include <wx/string.h>
31#include <wx/msgdlg.h>
32#include <wx/textdlg.h>
33
34#include <bitmaps.h>
35#include <bitmap_store.h>
36#include <gestfich.h>
37#include <macros.h>
38#include <menus_helpers.h>
39#include <trace_helpers.h>
42#include <core/kicad_algo.h>
43#include <paths.h>
44#include <launch_ext.h>
45#include <wx/dcclient.h>
46#include <wx/settings.h>
47
48#include "project_tree_item.h"
49#include "project_tree.h"
50#include "pgm_kicad.h"
51#include "kicad_id.h"
52#include "kicad_manager_frame.h"
53
54#include "project_tree_pane.h"
55
56
57/* Note about the project tree build process:
58 * Building the project tree can be *very* long if there are a lot of subdirectories in the
59 * working directory. Unfortunately, this happens easily if the project file *.pro is in the
60 * user's home directory.
61 * So the tree project is built "on demand":
62 * First the tree is built from the current directory and shows files and subdirs.
63 * > First level subdirs trees are built (i.e subdirs contents are not read)
64 * > When expanding a subdir, each subdir contains is read, and the corresponding sub tree is
65 * populated on the fly.
66 */
67
68// list of files extensions listed in the tree project window
69// Add extensions in a compatible regex format to see others files types
70static const wxChar* s_allowedExtensionsToList[] = {
71 wxT( "^.*\\.pro$" ),
72 wxT( "^.*\\.kicad_pro$" ),
73 wxT( "^.*\\.pdf$" ),
74 wxT( "^.*\\.sch$" ), // Legacy Eeschema files
75 wxT( "^.*\\.kicad_sch$" ), // S-expr Eeschema files
76 wxT( "^[^$].*\\.brd$" ), // Legacy Pcbnew files
77 wxT( "^[^$].*\\.kicad_pcb$" ), // S format Pcbnew board files
78 wxT( "^[^$].*\\.kicad_dru$" ), // Design rule files
79 wxT( "^[^$].*\\.kicad_wks$" ), // S format kicad drawing sheet files
80 wxT( "^[^$].*\\.kicad_mod$" ), // S format kicad footprint files, currently not listed
81 wxT( "^.*\\.net$" ), // pcbnew netlist file
82 wxT( "^.*\\.cir$" ), // Spice netlist file
83 wxT( "^.*\\.lib$" ), // Legacy schematic library file
84 wxT( "^.*\\.kicad_sym$" ), // S-expr symbol libraries
85 wxT( "^.*\\.txt$" ), // Text files
86 wxT( "^.*\\.md$" ), // Markdown files
87 wxT( "^.*\\.pho$" ), // Gerber file (Old Kicad extension)
88 wxT( "^.*\\.gbr$" ), // Gerber file
89 wxT( "^.*\\.gbrjob$" ), // Gerber job file
90 wxT( "^.*\\.gb[alops]$" ), // Gerber back (or bottom) layer file (deprecated Protel ext)
91 wxT( "^.*\\.gt[alops]$" ), // Gerber front (or top) layer file (deprecated Protel ext)
92 wxT( "^.*\\.g[0-9]{1,2}$" ), // Gerber inner layer file (deprecated Protel ext)
93 wxT( "^.*\\.gm[0-9]{1,2}$" ), // Gerber mechanical layer file (deprecated Protel ext)
94 wxT( "^.*\\.gko$" ), // Gerber keepout layer file (deprecated Protel ext)
95 wxT( "^.*\\.odt$" ),
96 wxT( "^.*\\.htm$" ),
97 wxT( "^.*\\.html$" ),
98 wxT( "^.*\\.rpt$" ), // Report files
99 wxT( "^.*\\.csv$" ), // Report files in comma separated format
100 wxT( "^.*\\.pos$" ), // Footprint position files
101 wxT( "^.*\\.cmp$" ), // CvPcb cmp/footprint link files
102 wxT( "^.*\\.drl$" ), // Excellon drill files
103 wxT( "^.*\\.nc$" ), // Excellon NC drill files (alternate file ext)
104 wxT( "^.*\\.xnc$" ), // Excellon NC drill files (alternate file ext)
105 wxT( "^.*\\.svg$" ), // SVG print/plot files
106 wxT( "^.*\\.ps$" ), // PostScript plot files
107 wxT( "^.*\\.zip$" ), // Zip archive files
108 nullptr // end of list
109};
110
111
119BEGIN_EVENT_TABLE( PROJECT_TREE_PANE, wxSashLayoutWindow )
120 EVT_TREE_ITEM_ACTIVATED( ID_PROJECT_TREE, PROJECT_TREE_PANE::onSelect )
121 EVT_TREE_ITEM_EXPANDED( ID_PROJECT_TREE, PROJECT_TREE_PANE::onExpand )
122 EVT_TREE_ITEM_RIGHT_CLICK( ID_PROJECT_TREE, PROJECT_TREE_PANE::onRight )
129 EVT_IDLE( PROJECT_TREE_PANE::onIdle )
130 EVT_PAINT( PROJECT_TREE_PANE::onPaint )
131END_EVENT_TABLE()
132
133
135 wxSashLayoutWindow( parent, ID_LEFT_FRAME, wxDefaultPosition, wxDefaultSize,
136 wxNO_BORDER | wxTAB_TRAVERSAL )
137{
138 m_Parent = parent;
139 m_TreeProject = nullptr;
140 m_isRenaming = false;
141 m_selectedItem = nullptr;
142 m_watcherNeedReset = false;
143
144 m_watcher = nullptr;
145 Connect( wxEVT_FSWATCHER,
146 wxFileSystemWatcherEventHandler( PROJECT_TREE_PANE::onFileSystemEvent ) );
147
148 Bind( wxEVT_SYS_COLOUR_CHANGED,
149 wxSysColourChangedEventHandler( PROJECT_TREE_PANE::onThemeChanged ), this );
150
151 /*
152 * Filtering is now inverted: the filters are actually used to _enable_ support
153 * for a given file type.
154 */
155 for( int ii = 0; s_allowedExtensionsToList[ii] != nullptr; ii++ )
156 m_filters.emplace_back( s_allowedExtensionsToList[ii] );
157
158 m_filters.emplace_back( wxT( "^no KiCad files found" ) );
159
160 ReCreateTreePrj();
161}
162
163
165{
167}
168
169
171{
172 if( m_watcher )
173 {
174 m_watcher->RemoveAll();
175 m_watcher->SetOwner( nullptr );
176 delete m_watcher;
177 m_watcher = nullptr;
178 }
179}
180
181
183{
184 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
185
186 if( tree_data.size() != 1 )
187 return;
188
189 wxString prj_filename = tree_data[0]->GetFileName();
190
191 m_Parent->LoadProject( prj_filename );
192}
193
194
195void PROJECT_TREE_PANE::onOpenDirectory( wxCommandEvent& event )
196{
197 // Get the root directory name:
198 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
199
200 for( PROJECT_TREE_ITEM* item_data : tree_data )
201 {
202 // Ask for the new sub directory name
203 wxString curr_dir = item_data->GetDir();
204
205 if( curr_dir.IsEmpty() )
206 {
207 // Use project path if the tree view path was empty.
208 curr_dir = wxPathOnly( m_Parent->GetProjectFileName() );
209
210 // As a last resort use the user's documents folder.
211 if( curr_dir.IsEmpty() || !wxFileName::DirExists( curr_dir ) )
213
214 if( !curr_dir.IsEmpty() )
215 curr_dir += wxFileName::GetPathSeparator();
216 }
217
218 LaunchExternal( curr_dir );
219 }
220}
221
222
223void PROJECT_TREE_PANE::onCreateNewDirectory( wxCommandEvent& event )
224{
225 // Get the root directory name:
226 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
227
228 for( PROJECT_TREE_ITEM* item_data : tree_data )
229 {
230 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
231
232 // Ask for the new sub directory name
233 wxString curr_dir = item_data->GetDir();
234
235 if( curr_dir.IsEmpty() )
236 curr_dir = prj_dir;
237
238 wxString new_dir = wxGetTextFromUser( _( "Directory name:" ), _( "Create New Directory" ) );
239
240 if( new_dir.IsEmpty() )
241 return;
242
243 wxString full_dirname = curr_dir + wxFileName::GetPathSeparator() + new_dir;
244
245 wxMkdir( full_dirname );
246 addItemToProjectTree( full_dirname, item_data->GetId(), nullptr, false );
247 }
248}
249
250
252{
253 switch( type )
254 {
272 case TREE_FILE_TYPE::DRILL_NC: return "nc";
273 case TREE_FILE_TYPE::DRILL_XNC: return "xnc";
281 default: return wxEmptyString;
282 }
283}
284
285
286std::vector<wxString> getProjects( const wxDir& dir )
287{
288 std::vector<wxString> projects;
289 wxString dir_filename;
290 bool haveFile = dir.GetFirst( &dir_filename );
291
292 while( haveFile )
293 {
294 wxFileName file( dir_filename );
295
296 if( file.GetExt() == LegacyProjectFileExtension || file.GetExt() == ProjectFileExtension )
297 projects.push_back( file.GetName() );
298
299 haveFile = dir.GetNext( &dir_filename );
300 }
301
302 return projects;
303}
304
305
306wxTreeItemId PROJECT_TREE_PANE::addItemToProjectTree( const wxString& aName,
307 const wxTreeItemId& aParent,
308 std::vector<wxString>* aProjectNames,
309 bool aRecurse )
310{
312 wxFileName fn( aName );
313
314 // Files/dirs names starting by "." are not visible files under unices.
315 // Skip them also under Windows
316 if( fn.GetName().StartsWith( wxT( "." ) ) )
317 return wxTreeItemId();
318
319 if( wxDirExists( aName ) )
320 {
322 }
323 else
324 {
325 // Filter
326 wxRegEx reg;
327 bool addFile = false;
328
329 for( const wxString& m_filter : m_filters )
330 {
331 wxCHECK2_MSG( reg.Compile( m_filter, wxRE_ICASE ), continue,
332 wxString::Format( "Regex %s failed to compile.", m_filter ) );
333
334 if( reg.Matches( aName ) )
335 {
336 addFile = true;
337 break;
338 }
339 }
340
341 if( !addFile )
342 return wxTreeItemId();
343
344 for( int i = static_cast<int>( TREE_FILE_TYPE::LEGACY_PROJECT );
345 i < static_cast<int>( TREE_FILE_TYPE::MAX ); i++ )
346 {
347 wxString ext = GetFileExt( (TREE_FILE_TYPE) i );
348
349 if( ext == wxT( "" ) )
350 continue;
351
352 reg.Compile( wxString::FromAscii( "^.*\\." ) + ext + wxString::FromAscii( "$" ),
353 wxRE_ICASE );
354
355 if( reg.Matches( aName ) )
356 {
357 type = (TREE_FILE_TYPE) i;
358 break;
359 }
360 }
361 }
362
363 wxString file = wxFileNameFromPath( aName );
364 wxFileName currfile( file );
365 wxFileName project( m_Parent->GetProjectFileName() );
366
367 // Ignore legacy projects with the same name as the current project
368 if( ( type == TREE_FILE_TYPE::LEGACY_PROJECT )
369 && ( currfile.GetName().CmpNoCase( project.GetName() ) == 0 ) )
370 {
371 return wxTreeItemId();
372 }
373
374 if( currfile.GetExt() == GetFileExt( TREE_FILE_TYPE::LEGACY_SCHEMATIC )
375 || currfile.GetExt() == GetFileExt( TREE_FILE_TYPE::SEXPR_SCHEMATIC ) )
376 {
377 if( aProjectNames )
378 {
379 if( !alg::contains( *aProjectNames, currfile.GetName() ) )
380 return wxTreeItemId();
381 }
382 else
383 {
384 PROJECT_TREE_ITEM* parentTreeItem = GetItemIdData( aParent );
385 wxDir parentDir( parentTreeItem->GetDir() );
386 std::vector<wxString> projects = getProjects( parentDir );
387
388 if( !alg::contains( projects, currfile.GetName() ) )
389 return wxTreeItemId();
390 }
391 }
392
393 // also check to see if it is already there.
394 wxTreeItemIdValue cookie;
395 wxTreeItemId kid = m_TreeProject->GetFirstChild( aParent, cookie );
396
397 while( kid.IsOk() )
398 {
399 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
400
401 if( itemData && itemData->GetFileName() == aName )
402 return itemData->GetId(); // well, we would have added it, but it is already here!
403
404 kid = m_TreeProject->GetNextChild( aParent, cookie );
405 }
406
407 // Only show current files if both legacy and current files are present
410 {
411 kid = m_TreeProject->GetFirstChild( aParent, cookie );
412
413 while( kid.IsOk() )
414 {
415 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
416
417 if( itemData )
418 {
419 wxFileName fname( itemData->GetFileName() );
420
421 if( fname.GetName().CmpNoCase( currfile.GetName() ) == 0 )
422 {
423 switch( type )
424 {
426 if( itemData->GetType() == TREE_FILE_TYPE::JSON_PROJECT )
427 return wxTreeItemId();
428
429 break;
430
432 if( itemData->GetType() == TREE_FILE_TYPE::SEXPR_SCHEMATIC )
433 return wxTreeItemId();
434
435 break;
436
438 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_PROJECT )
439 m_TreeProject->Delete( kid );
440
441 break;
442
444 if( itemData->GetType() == TREE_FILE_TYPE::LEGACY_SCHEMATIC )
445 m_TreeProject->Delete( kid );
446
447 break;
448
449 default:
450 break;
451 }
452 }
453 }
454
455 kid = m_TreeProject->GetNextChild( aParent, cookie );
456 }
457 }
458
459 // Append the item (only appending the filename not the full path):
460 wxTreeItemId newItemId = m_TreeProject->AppendItem( aParent, file );
461 PROJECT_TREE_ITEM* data = new PROJECT_TREE_ITEM( type, aName, m_TreeProject );
462
463 m_TreeProject->SetItemData( newItemId, data );
464 data->SetState( 0 );
465
466 // Mark root files (files which have the same aName as the project)
467 wxString fileName = currfile.GetName().Lower();
468 wxString projName = project.GetName().Lower();
469 data->SetRootFile( fileName == projName || fileName.StartsWith( projName + "-" ) );
470
471#ifndef __WINDOWS__
472 bool subdir_populated = false;
473#endif
474
475 // This section adds dirs and files found in the subdirs
476 // in this case AddFile is recursive, but for the first level only.
477 if( TREE_FILE_TYPE::DIRECTORY == type && aRecurse )
478 {
479 wxDir dir( aName );
480
481 if( dir.IsOpened() ) // protected dirs will not open properly.
482 {
483 std::vector<wxString> projects = getProjects( dir );
484 wxString dir_filename;
485 bool haveFile = dir.GetFirst( &dir_filename );
486
487 data->SetPopulated( true );
488
489#ifndef __WINDOWS__
490 subdir_populated = aRecurse;
491#endif
492
493 while( haveFile )
494 {
495 // Add name in tree, but do not recurse
496 wxString path = aName + wxFileName::GetPathSeparator() + dir_filename;
497 addItemToProjectTree( path, newItemId, &projects, false );
498
499 haveFile = dir.GetNext( &dir_filename );
500 }
501 }
502
503 // Sort filenames by alphabetic order
504 m_TreeProject->SortChildren( newItemId );
505 }
506
507#ifndef __WINDOWS__
508 if( subdir_populated )
509 m_watcherNeedReset = true;
510#endif
511
512 return newItemId;
513}
514
515
517{
518 wxString pro_dir = m_Parent->GetProjectFileName();
519
520 if( !m_TreeProject )
521 m_TreeProject = new PROJECT_TREE( this );
522 else
523 m_TreeProject->DeleteAllItems();
524
525 if( !pro_dir ) // This is empty from PROJECT_TREE_PANE constructor
526 return;
527
528 wxFileName fn = pro_dir;
529 bool prjReset = false;
530
531 if( !fn.IsOk() )
532 {
533 fn.Clear();
534 fn.SetPath( PATHS::GetDefaultUserProjectsPath() );
535 fn.SetName( NAMELESS_PROJECT );
536 fn.SetExt( ProjectFileExtension );
537 prjReset = true;
538 }
539
540 bool prjOpened = fn.FileExists();
541
542 // We may have opened a legacy project, in which case GetProjectFileName will return the
543 // name of the migrated (new format) file, which may not have been saved to disk yet.
544 if( !prjOpened && !prjReset )
545 {
546 fn.SetExt( LegacyProjectFileExtension );
547 prjOpened = fn.FileExists();
548
549 // Set the ext back so that in the tree view we see the (not-yet-saved) new file
550 fn.SetExt( ProjectFileExtension );
551 }
552
553 // root tree:
554 m_root = m_TreeProject->AddRoot( fn.GetFullName(), static_cast<int>( TREE_FILE_TYPE::ROOT ),
555 static_cast<int>( TREE_FILE_TYPE::ROOT ) );
556 m_TreeProject->SetItemBold( m_root, true );
557
558 // The main project file is now a JSON file
560 fn.GetFullPath(), m_TreeProject ) );
561
562 // Now adding all current files if available
563 if( prjOpened )
564 {
565 pro_dir = wxPathOnly( m_Parent->GetProjectFileName() );
566 wxDir dir( pro_dir );
567
568 if( dir.IsOpened() ) // protected dirs will not open, see "man opendir()"
569 {
570 std::vector<wxString> projects = getProjects( dir );
571 wxString filename;
572 bool haveFile = dir.GetFirst( &filename );
573
574 while( haveFile )
575 {
576 if( filename != fn.GetFullName() )
577 {
578 wxString name = dir.GetName() + wxFileName::GetPathSeparator() + filename;
579 // Add items living in the project directory, and populate the item
580 // if it is a directory (sub directories will be not populated)
581 addItemToProjectTree( name, m_root, &projects, true );
582 }
583
584 haveFile = dir.GetNext( &filename );
585 }
586 }
587 }
588 else
589 {
590 m_TreeProject->AppendItem( m_root, wxT( "Empty project" ) );
591 }
592
593 m_TreeProject->Expand( m_root );
594
595 // Sort filenames by alphabetic order
596 m_TreeProject->SortChildren( m_root );
597}
598
599
600void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event )
601{
602 wxTreeItemId curr_item = Event.GetItem();
603
604 // Ensure item is selected (Under Windows right click does not select the item)
605 m_TreeProject->SelectItem( curr_item );
606
607 std::vector<PROJECT_TREE_ITEM*> selection = GetSelectedData();
608
609 bool can_switch_to_project = true;
610 bool can_create_new_directory = true;
611 bool can_open_this_directory = true;
612 bool can_edit = true;
613 bool can_rename = true;
614 bool can_delete = true;
615
616 if( selection.size() == 0 )
617 return;
618
619 // Remove things that don't make sense for multiple selections
620 if( selection.size() != 1 )
621 {
622 can_switch_to_project = false;
623 can_create_new_directory = false;
624 can_rename = false;
625 }
626
627 for( PROJECT_TREE_ITEM* item : selection )
628 {
629 // Check for empty project
630 if( !item )
631 {
632 can_switch_to_project = false;
633 can_edit = false;
634 can_rename = false;
635 continue;
636 }
637
638 can_delete = item->CanDelete();
639 can_rename = item->CanRename();
640
641 switch( item->GetType() )
642 {
645 can_rename = false;
646
647 if( item->GetId() == m_TreeProject->GetRootItem() )
648 {
649 can_switch_to_project = false;
650 }
651 else
652 {
653 can_create_new_directory = false;
654 can_open_this_directory = false;
655 }
656 break;
657
659 can_switch_to_project = false;
660 can_edit = false;
661 break;
662
663 default:
664 can_switch_to_project = false;
665 can_create_new_directory = false;
666 can_open_this_directory = false;
667
668 break;
669 }
670 }
671
672 wxMenu popup_menu;
673 wxString text;
674 wxString help_text;
675
676 if( can_switch_to_project )
677 {
679 _( "Switch to this Project" ),
680 _( "Close all editors, and switch to the selected project" ),
682 popup_menu.AppendSeparator();
683 }
684
685 if( can_create_new_directory )
686 {
687 AddMenuItem( &popup_menu, ID_PROJECT_NEWDIR, _( "New Directory..." ),
688 _( "Create a New Directory" ), KiBitmap( BITMAPS::directory ) );
689 }
690
691 if( can_open_this_directory )
692 {
693 if( selection.size() == 1 )
694 {
695#ifdef __APPLE__
696 text = _( "Reveal in Finder" );
697 help_text = _( "Reveals the directory in a Finder window" );
698#else
699 text = _( "Open Directory in File Explorer" );
700 help_text = _( "Opens the directory in the default system file manager" );
701#endif
702 }
703 else
704 {
705#ifdef __APPLE__
706 text = _( "Reveal in Finder" );
707 help_text = _( "Reveals the directories in a Finder window" );
708#else
709 text = _( "Open Directories in File Explorer" );
710 help_text = _( "Opens the directories in the default system file manager" );
711#endif
712 }
713
714 AddMenuItem( &popup_menu, ID_PROJECT_OPEN_DIR, text, help_text,
716 }
717
718 if( can_edit )
719 {
720 if( selection.size() == 1 )
721 help_text = _( "Open the file in a Text Editor" );
722 else
723 help_text = _( "Open files in a Text Editor" );
724
725 AddMenuItem( &popup_menu, ID_PROJECT_TXTEDIT, _( "Edit in a Text Editor" ),
726 help_text, KiBitmap( BITMAPS::editor ) );
727 }
728
729 if( can_rename )
730 {
731 if( selection.size() == 1 )
732 {
733 text = _( "Rename File..." );
734 help_text = _( "Rename file" );
735 }
736 else
737 {
738 text = _( "Rename Files..." );
739 help_text = _( "Rename files" );
740 }
741
742 AddMenuItem( &popup_menu, ID_PROJECT_RENAME, text, help_text, KiBitmap( BITMAPS::right ) );
743 }
744
745 if( can_delete )
746 {
747 if( selection.size() == 1 )
748 help_text = _( "Delete the file and its content" );
749 else
750 help_text = _( "Delete the files and their contents" );
751
752 if( can_switch_to_project
753 || can_create_new_directory
754 || can_open_this_directory
755 || can_edit
756 || can_rename )
757 {
758 popup_menu.AppendSeparator();
759 }
760
761#ifdef __WINDOWS__
762 AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Delete" ), help_text,
764#else
765 AddMenuItem( &popup_menu, ID_PROJECT_DELETE, _( "Move to Trash" ), help_text,
767#endif
768 }
769
770 if( popup_menu.GetMenuItemCount() > 0 )
771 PopupMenu( &popup_menu );
772}
773
774
776{
777 wxString editorname = Pgm().GetTextEditor();
778
779 if( editorname.IsEmpty() )
780 {
781 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
782 return;
783 }
784
785 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
786 wxString files;
787
788 for( PROJECT_TREE_ITEM* item_data : tree_data )
789 {
790 wxString fullFileName = item_data->GetFileName();
791
792 if( !files.IsEmpty() )
793 files += " ";
794
795 files += fullFileName;
796 }
797
798 ExecuteFile( editorname, files );
799}
800
801
802void PROJECT_TREE_PANE::onDeleteFile( wxCommandEvent& event )
803{
804 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
805
806 for( PROJECT_TREE_ITEM* item_data : tree_data )
807 item_data->Delete();
808}
809
810
811void PROJECT_TREE_PANE::onRenameFile( wxCommandEvent& event )
812{
813 wxTreeItemId curr_item = m_TreeProject->GetFocusedItem();
814 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
815
816 // XXX: Unnecessary?
817 if( tree_data.size() != 1 )
818 return;
819
820 wxString buffer = m_TreeProject->GetItemText( curr_item );
821 wxString msg = wxString::Format( _( "Change filename: '%s'" ),
822 tree_data[0]->GetFileName() );
823 wxTextEntryDialog dlg( this, msg, _( "Change filename" ), buffer );
824
825 if( dlg.ShowModal() != wxID_OK )
826 return; // canceled by user
827
828 buffer = dlg.GetValue();
829 buffer.Trim( true );
830 buffer.Trim( false );
831
832 if( buffer.IsEmpty() )
833 return; // empty file name not allowed
834
835 tree_data[0]->Rename( buffer, true );
836 m_isRenaming = true;
837}
838
839
840void PROJECT_TREE_PANE::onSelect( wxTreeEvent& Event )
841{
842 std::vector<PROJECT_TREE_ITEM*> tree_data = GetSelectedData();
843
844 if( tree_data.size() != 1 )
845 return;
846
847 // Bookmark the selected item but don't try and activate it until later. If we do it now,
848 // there will be more events at least on Windows in this frame that will steal focus from
849 // any newly launched windows
850 m_selectedItem = tree_data[0];
851}
852
853
854void PROJECT_TREE_PANE::onIdle( wxIdleEvent& aEvent )
855{
856 // Idle executes once all other events finished processing. This makes it ideal to launch
857 // a new window without starting Focus wars.
859 {
860 m_selectedItem = nullptr;
862 }
863
864 if( m_selectedItem != nullptr )
865 {
866 // Activate launches a window which may run the event loop on top of us and cause OnIdle
867 // to get called again, so be sure to block off the activation condition first.
869 m_selectedItem = nullptr;
870
871 item->Activate( this );
872 }
873}
874
875
876void PROJECT_TREE_PANE::onExpand( wxTreeEvent& Event )
877{
878 wxTreeItemId itemId = Event.GetItem();
879 PROJECT_TREE_ITEM* tree_data = GetItemIdData( itemId );
880
881 if( !tree_data )
882 return;
883
884 if( tree_data->GetType() != TREE_FILE_TYPE::DIRECTORY )
885 return;
886
887 // explore list of non populated subdirs, and populate them
888 wxTreeItemIdValue cookie;
889 wxTreeItemId kid = m_TreeProject->GetFirstChild( itemId, cookie );
890
891#ifndef __WINDOWS__
892 bool subdir_populated = false;
893#endif
894
895 for( ; kid.IsOk(); kid = m_TreeProject->GetNextChild( itemId, cookie ) )
896 {
897 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
898
899 if( !itemData || itemData->GetType() != TREE_FILE_TYPE::DIRECTORY )
900 continue;
901
902 if( itemData->IsPopulated() )
903 continue;
904
905 wxString fileName = itemData->GetFileName();
906 wxDir dir( fileName );
907
908 if( dir.IsOpened() )
909 {
910 std::vector<wxString> projects = getProjects( dir );
911 wxString dir_filename;
912 bool haveFile = dir.GetFirst( &dir_filename );
913
914 while( haveFile )
915 {
916 // Add name to tree item, but do not recurse in subdirs:
917 wxString name = fileName + wxFileName::GetPathSeparator() + dir_filename;
918 addItemToProjectTree( name, kid, &projects, false );
919
920 haveFile = dir.GetNext( &dir_filename );
921 }
922
923 itemData->SetPopulated( true ); // set state to populated
924
925#ifndef __WINDOWS__
926 subdir_populated = true;
927#endif
928 }
929
930 // Sort filenames by alphabetic order
931 m_TreeProject->SortChildren( kid );
932 }
933
934#ifndef __WINDOWS__
935 if( subdir_populated )
936 m_watcherNeedReset = true;
937#endif
938}
939
940
941std::vector<PROJECT_TREE_ITEM*> PROJECT_TREE_PANE::GetSelectedData()
942{
943 wxArrayTreeItemIds selection;
944 std::vector<PROJECT_TREE_ITEM*> data;
945
946 m_TreeProject->GetSelections( selection );
947
948 for( auto it = selection.begin(); it != selection.end(); it++ )
949 data.push_back( GetItemIdData( *it ) );
950
951 return data;
952}
953
954
956{
957 return dynamic_cast<PROJECT_TREE_ITEM*>( m_TreeProject->GetItemData( aId ) );
958}
959
960
961wxTreeItemId PROJECT_TREE_PANE::findSubdirTreeItem( const wxString& aSubDir )
962{
963 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
964
965 // If the subdir is the current working directory, return m_root
966 // in main list:
967 if( prj_dir == aSubDir )
968 return m_root;
969
970 // The subdir is in the main tree or in a subdir: Locate it
971 wxTreeItemIdValue cookie;
972 wxTreeItemId root_id = m_root;
973 std::stack<wxTreeItemId> subdirs_id;
974
975 wxTreeItemId child = m_TreeProject->GetFirstChild( root_id, cookie );
976
977 while( true )
978 {
979 if( ! child.IsOk() )
980 {
981 if( subdirs_id.empty() ) // all items were explored
982 {
983 root_id = child; // Not found: return an invalid wxTreeItemId
984 break;
985 }
986 else
987 {
988 root_id = subdirs_id.top();
989 subdirs_id.pop();
990 child = m_TreeProject->GetFirstChild( root_id, cookie );
991
992 if( !child.IsOk() )
993 continue;
994 }
995 }
996
997 PROJECT_TREE_ITEM* itemData = GetItemIdData( child );
998
999 if( itemData && ( itemData->GetType() == TREE_FILE_TYPE::DIRECTORY ) )
1000 {
1001 if( itemData->GetFileName() == aSubDir ) // Found!
1002 {
1003 root_id = child;
1004 break;
1005 }
1006
1007 // child is a subdir, push in list to explore it later
1008 if( itemData->IsPopulated() )
1009 subdirs_id.push( child );
1010 }
1011
1012 child = m_TreeProject->GetNextChild( root_id, cookie );
1013 }
1014
1015 return root_id;
1016}
1017
1018
1019void PROJECT_TREE_PANE::onFileSystemEvent( wxFileSystemWatcherEvent& event )
1020{
1021 // No need to process events when we're shutting down
1022 if( !m_watcher )
1023 return;
1024
1025 const wxFileName& pathModified = event.GetPath();
1026 wxString subdir = pathModified.GetPath();
1027 wxString fn = pathModified.GetFullPath();
1028
1029 switch( event.GetChangeType() )
1030 {
1031 case wxFSW_EVENT_DELETE:
1032 case wxFSW_EVENT_CREATE:
1033 case wxFSW_EVENT_RENAME:
1034 break;
1035
1036 case wxFSW_EVENT_MODIFY:
1037 case wxFSW_EVENT_ACCESS:
1038 default:
1039 return;
1040 }
1041
1042 wxTreeItemId root_id = findSubdirTreeItem( subdir );
1043
1044 if( !root_id.IsOk() )
1045 return;
1046
1047 wxTreeItemIdValue cookie; // dummy variable needed by GetFirstChild()
1048 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1049
1050 switch( event.GetChangeType() )
1051 {
1052 case wxFSW_EVENT_CREATE:
1053 {
1054 wxTreeItemId newitem =
1055 addItemToProjectTree( pathModified.GetFullPath(), root_id, nullptr, true );
1056
1057 // If we are in the process of renaming a file, select the new one
1058 // This is needed for MSW and OSX, since we don't get RENAME events from them, just a
1059 // pair of DELETE and CREATE events.
1060 if( m_isRenaming && newitem.IsOk() )
1061 {
1062 m_TreeProject->SelectItem( newitem );
1063 m_isRenaming = false;
1064 }
1065 }
1066 break;
1067
1068 case wxFSW_EVENT_DELETE:
1069 while( kid.IsOk() )
1070 {
1071 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1072
1073 if( itemData && itemData->GetFileName() == fn )
1074 {
1075 m_TreeProject->Delete( kid );
1076 return;
1077 }
1078 kid = m_TreeProject->GetNextChild( root_id, cookie );
1079 }
1080 break;
1081
1082 case wxFSW_EVENT_RENAME :
1083 {
1084 const wxFileName& newpath = event.GetNewPath();
1085 wxString newdir = newpath.GetPath();
1086 wxString newfn = newpath.GetFullPath();
1087
1088 while( kid.IsOk() )
1089 {
1090 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1091
1092 if( itemData && itemData->GetFileName() == fn )
1093 {
1094 m_TreeProject->Delete( kid );
1095 break;
1096 }
1097
1098 kid = m_TreeProject->GetNextChild( root_id, cookie );
1099 }
1100
1101 // Add the new item only if it is not the current project file (root item).
1102 // Remember: this code is called by a wxFileSystemWatcherEvent event, and not always
1103 // called after an actual file rename, and the cleanup code does not explore the
1104 // root item, because it cannot be renamed by the user. Also, ensure the new file
1105 // actually exists on the file system before it is readded. On Linux, moving a file
1106 // to the trash can cause the same path to be returned in both the old and new paths
1107 // of the event, even though the file isn't there anymore.
1108 PROJECT_TREE_ITEM* rootData = GetItemIdData( root_id );
1109
1110 if( newpath.Exists() && ( newfn != rootData->GetFileName() ) )
1111 {
1112 wxTreeItemId newroot_id = findSubdirTreeItem( newdir );
1113 wxTreeItemId newitem = addItemToProjectTree( newfn, newroot_id, nullptr, true );
1114
1115 // If the item exists, select it
1116 if( newitem.IsOk() )
1117 m_TreeProject->SelectItem( newitem );
1118 }
1119
1120 m_isRenaming = false;
1121 }
1122 break;
1123 }
1124
1125 // Sort filenames by alphabetic order
1126 m_TreeProject->SortChildren( root_id );
1127}
1128
1129
1131{
1132 m_watcherNeedReset = false;
1133
1134 wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1135
1136#if defined( _WIN32 )
1137 if( KIPLATFORM::ENV::IsNetworkPath( prj_dir ) )
1138 {
1139 // Due to a combination of a bug in SAMBA sending bad change event IDs and wxWidgets
1140 // choosing to fault on an invalid event ID instead of sanely ignoring them we need to
1141 // avoid spawning a filewatcher. Unfortunately this punishes corporate environments with
1142 // Windows Server shares :/
1143 m_Parent->SetStatusText( _( "Network path: not monitoring folder changes" ), 1 );
1144 return;
1145 }
1146 else
1147 {
1148 m_Parent->SetStatusText( _( "Local path: monitoring folder changes" ), 1 );
1149 }
1150#endif
1151
1152 // Prepare file watcher:
1153 if( m_watcher )
1154 {
1155 m_watcher->RemoveAll();
1156 }
1157 else
1158 {
1159 m_watcher = new wxFileSystemWatcher();
1160 m_watcher->SetOwner( this );
1161 }
1162
1163 // We can see wxString under a debugger, not a wxFileName
1164 wxFileName fn;
1165 fn.AssignDir( prj_dir );
1166 fn.DontFollowLink();
1167
1168 // Add directories which should be monitored.
1169 // under windows, we add the curr dir and all subdirs
1170 // under unix, we add only the curr dir and the populated subdirs
1171 // see http://docs.wxwidgets.org/trunk/classwx_file_system_watcher.htm
1172 // under unix, the file watcher needs more work to be efficient
1173 // moreover, under wxWidgets 2.9.4, AddTree does not work properly.
1174#ifdef __WINDOWS__
1175 m_watcher->AddTree( fn );
1176#else
1177 m_watcher->Add( fn );
1178
1179 if( m_TreeProject->IsEmpty() )
1180 return;
1181
1182 // Add subdirs
1183 wxTreeItemIdValue cookie;
1184 wxTreeItemId root_id = m_root;
1185
1186 std::stack < wxTreeItemId > subdirs_id;
1187
1188 wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1189
1190 while( true )
1191 {
1192 if( !kid.IsOk() )
1193 {
1194 if( subdirs_id.empty() ) // all items were explored
1195 {
1196 break;
1197 }
1198 else
1199 {
1200 root_id = subdirs_id.top();
1201 subdirs_id.pop();
1202 kid = m_TreeProject->GetFirstChild( root_id, cookie );
1203
1204 if( !kid.IsOk() )
1205 continue;
1206 }
1207 }
1208
1209 PROJECT_TREE_ITEM* itemData = GetItemIdData( kid );
1210
1211 if( itemData && itemData->GetType() == TREE_FILE_TYPE::DIRECTORY )
1212 {
1213 // we can see wxString under a debugger, not a wxFileName
1214 const wxString& path = itemData->GetFileName();
1215
1216 wxLogTrace( tracePathsAndFiles, "%s: add '%s'\n", __func__, TO_UTF8( path ) );
1217
1218 if( wxFileName::IsDirReadable( path ) ) // linux whines about watching protected dir
1219 {
1220 fn.AssignDir( path );
1221 m_watcher->Add( fn );
1222
1223 // if kid is a subdir, push in list to explore it later
1224 if( itemData->IsPopulated() && m_TreeProject->GetChildrenCount( kid ) )
1225 subdirs_id.push( kid );
1226 }
1227 }
1228
1229 kid = m_TreeProject->GetNextChild( root_id, cookie );
1230 }
1231#endif
1232
1233#if defined(DEBUG) && 1
1234 wxArrayString paths;
1235 m_watcher->GetWatchedPaths( &paths );
1236 wxLogTrace( tracePathsAndFiles, "%s: watched paths:", __func__ );
1237
1238 for( unsigned ii = 0; ii < paths.GetCount(); ii++ )
1239 wxLogTrace( tracePathsAndFiles, " %s\n", TO_UTF8( paths[ii] ) );
1240#endif
1241}
1242
1243
1245{
1246 // Make sure we don't try to inspect the tree after we've deleted its items.
1248
1249 m_TreeProject->DeleteAllItems();
1250}
1251
1252
1253void PROJECT_TREE_PANE::onThemeChanged( wxSysColourChangedEvent &aEvent )
1254{
1257 m_TreeProject->Refresh();
1258
1259 aEvent.Skip();
1260}
1261
1262
1263void PROJECT_TREE_PANE::onPaint( wxPaintEvent& event )
1264{
1265 wxRect rect( wxPoint( 0, 0 ), GetClientSize() );
1266 wxPaintDC dc( this );
1267
1268 dc.SetBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_FRAMEBK ) );
1269 dc.SetPen( wxPen( wxSystemSettings::GetColour( wxSYS_COLOUR_ACTIVEBORDER ), 1 ) );
1270
1271 dc.DrawLine( rect.GetLeft(), rect.GetTop(), rect.GetLeft(), rect.GetBottom() );
1272 dc.DrawLine( rect.GetRight(), rect.GetTop(), rect.GetRight(), rect.GetBottom() );
1273}
1274
1275
1276void KICAD_MANAGER_FRAME::OnChangeWatchedPaths( wxCommandEvent& aEvent )
1277{
1279}
const char * name
Definition: DXF_plotter.cpp:56
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:105
wxMenuItem * AddMenuItem(wxMenu *aMenu, int aId, const wxString &aText, const wxBitmap &aImage, wxItemKind aType=wxITEM_NORMAL)
Create and insert a menu item with an icon into aMenu.
Definition: bitmap.cpp:265
BITMAP_STORE * GetBitmapStore()
Definition: bitmap.cpp:93
@ directory_browser
@ open_project
void ThemeChanged()
Notifies the store that the icon theme has been changed by the user, so caches must be invalidated.
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 wxString GetDefaultUserProjectsPath()
Gets the default path we point users to create projects.
Definition: paths.cpp:139
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 onDeleteFile(wxCommandEvent &event)
Function onDeleteFile Delete the selected file or directory in the tree project.
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 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 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...
static wxString GetFileExt(TREE_FILE_TYPE type)
friend class PROJECT_TREE_ITEM
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 shutdownFileWatcher()
Shutdown the file watcher.
wxTreeItemId addItemToProjectTree(const wxString &aName, const wxTreeItemId &aParent, std::vector< wxString > *aProjectNames, bool aRecurse)
Function addItemToProjectTree.
void onOpenSelectedFileWithTextEditor(wxCommandEvent &event)
Function onOpenSelectedFileWithTextEditor Call the text editor to open the selected file in the tree ...
PROJECT_TREE_ITEM * GetItemIdData(wxTreeItemId aId)
Function GetItemIdData return the item data corresponding to a wxTreeItemId identifier.
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.
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:39
void LoadIcons()
#define _(s)
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback)
Call the executable file aEditorName with the parameter aFileName.
Definition: gestfich.cpp:115
const std::string LegacyPcbFileExtension
const std::string PdfFileExtension
const std::string KiCadSymbolLibFileExtension
const std::string LegacySchematicFileExtension
const std::string FootprintAssignmentFileExtension
const std::string FootprintPlaceFileExtension
const std::string GerberJobFileExtension
const std::string DrillFileExtension
const wxString GerberFileExtensionsRegex
const std::string KiCadFootprintFileExtension
const std::string LegacyProjectFileExtension
const std::string MarkdownFileExtension
const std::string KiCadPcbFileExtension
const std::string SVGFileExtension
const std::string TextFileExtension
const std::string ProjectFileExtension
const std::string NetlistFileExtension
const std::string DesignRulesFileExtension
const std::string HtmlFileExtension
const std::string KiCadSchematicFileExtension
const std::string ReportFileExtension
const std::string DrawingSheetFileExtension
const std::string ArchiveFileExtension
const std::string LegacySymbolLibFileExtension
const wxChar *const tracePathsAndFiles
Flag to enable path and file name debug output.
IDs used in KiCad main frame foe menuitems and tools.
@ ID_PROJECT_OPEN_DIR
Definition: kicad_id.h:62
@ 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_PROJECT_TXTEDIT
Definition: kicad_id.h:59
@ ID_PROJECT_TREE
Definition: kicad_id.h:58
@ 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 TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:96
Macros and inline functions to create menus items in menubars or popup menus.
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 contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition: kicad_algo.h:99
#define NAMELESS_PROJECT
default name for nameless projects
Definition: project.h:42
std::vector< wxString > getProjects(const wxDir &dir)
static const wxChar * s_allowedExtensionsToList[]
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:111
wxLogTrace helper definitions.
TREE_FILE_TYPE
Definition of file extensions used in Kicad.