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