KiCad PCB EDA Suite
tree_project_frame.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-2020 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 
32 #include <stack>
33 
34 #include <wx/regex.h>
35 #include <wx/stdpaths.h>
36 #include <wx/string.h>
37 
38 #include <bitmaps.h>
39 #include <gestfich.h>
40 #include <menus_helpers.h>
41 #include <trace_helpers.h>
43 
44 #include "treeproject_item.h"
45 #include "treeprojectfiles.h"
46 #include "pgm_kicad.h"
47 #include "kicad_id.h"
48 
49 #include "tree_project_frame.h"
50 
51 
52 /* Note about the tree project build process:
53  * Building the tree project can be *very* long if there are a lot of subdirectories
54  * in the working directory.
55  * Unfortunately, this happens easily if the project file *.pro is in the 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,
60  * and the corresponding sub tree is 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( "^.*\\.pdf$" ),
68  wxT( "^.*\\.sch$" ), // Legacy Eeschema files
69  wxT( "^.*\\.kicad_sch$" ), // S-expr Eeschema files
70  wxT( "^[^$].*\\.brd$" ), // Legacy Pcbnew files
71  wxT( "^[^$].*\\.kicad_pcb$" ), // S format Pcbnew board files
72  wxT( "^[^$].*\\.kicad_wks$" ), // S format kicad page layout help_textr files
73  wxT( "^[^$].*\\.kicad_mod$" ), // S format kicad footprint files, currently not listed
74  wxT( "^.*\\.net$" ), // pcbnew netlist file
75  wxT( "^.*\\.cir$" ), // Spice netlist file
76  wxT( "^.*\\.lib$" ), // Legacy schematic library file
77  wxT( "^.*\\.kicad_sym$" ), // S-expr symbol libraries
78  wxT( "^.*\\.txt$" ),
79  wxT( "^.*\\.pho$" ), // Gerber file (Old Kicad extension)
80  wxT( "^.*\\.gbr$" ), // Gerber file
81  wxT( "^.*\\.gbrjob$" ), // Gerber job file
82  wxT( "^.*\\.gb[alops]$" ), // Gerber back (or bottom) layer file (deprecated Protel ext)
83  wxT( "^.*\\.gt[alops]$" ), // Gerber front (or top) layer file (deprecated Protel ext)
84  wxT( "^.*\\.g[0-9]{1,2}$" ), // Gerber inner layer file (deprecated Protel ext)
85  wxT( "^.*\\.odt$" ),
86  wxT( "^.*\\.htm$" ),
87  wxT( "^.*\\.html$" ),
88  wxT( "^.*\\.rpt$" ), // Report files
89  wxT( "^.*\\.csv$" ), // Report files in comma separated format
90  wxT( "^.*\\.pos$" ), // Footprint position files
91  wxT( "^.*\\.cmp$" ), // Cvpcb cmp/footprint link files
92  wxT( "^.*\\.drl$" ), // Excellon drill files
93  wxT( "^.*\\.nc$" ), // Excellon NC drill files (alternate file ext)
94  wxT( "^.*\\.xnc$" ), // Excellon NC drill files (alternate file ext)
95  wxT( "^.*\\.svg$" ), // SVG print/plot files
96  wxT( "^.*\\.ps$" ), // Postscript plot files
97  NULL // end of list
98 };
99 
100 
101 /* TODO: Check if these file extension and wildcard definitions are used
102  * in any of the other KiCad programs and move them into the common
103  * library as required.
104  */
105 
106 // File extension definitions.
107 const wxChar TextFileExtension[] = wxT( "txt" );
108 
109 // Gerber file extension wildcard.
110 const wxString GerberFileExtensionWildCard( ".((gbr|gbrjob|(gb|gt)[alops])|pho)" );
111 
112 
120 BEGIN_EVENT_TABLE( TREE_PROJECT_FRAME, wxSashLayoutWindow )
121  EVT_TREE_ITEM_ACTIVATED( ID_PROJECT_TREE, TREE_PROJECT_FRAME::OnSelect )
122  EVT_TREE_ITEM_EXPANDED( ID_PROJECT_TREE, TREE_PROJECT_FRAME::OnExpand )
123  EVT_TREE_ITEM_RIGHT_CLICK( ID_PROJECT_TREE, TREE_PROJECT_FRAME::OnRight )
131 END_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 = NULL;
140  m_isRenaming = false;
141 
142  m_watcher = NULL;
143  Connect( wxEVT_FSWATCHER,
144  wxFileSystemWatcherEventHandler( TREE_PROJECT_FRAME::OnFileSystemEvent ) );
145 
146  /*
147  * Filtering is now inverted: the filters are actually used to _enable_ support
148  * for a given file type.
149  */
150  for( int ii = 0; s_allowedExtensionsToList[ii] != NULL; ii++ )
151  m_filters.emplace_back( s_allowedExtensionsToList[ii] );
152 
153  m_filters.emplace_back( wxT( "^no KiCad files found" ) );
154 
155  ReCreateTreePrj();
156 }
157 
158 
160 {
161  if( m_watcher )
162  {
163  m_watcher->RemoveAll();
164  m_watcher->SetOwner( NULL );
165  delete m_watcher;
166  }
167 }
168 
169 
171 {
172  std::vector<TREEPROJECT_ITEM*> tree_data = GetSelectedData();
173 
174  if( tree_data.size() != 1 )
175  return;
176 
177  wxString prj_filename = tree_data[0]->GetFileName();
178 
179  m_Parent->LoadProject( prj_filename );
180 }
181 
182 
183 void TREE_PROJECT_FRAME::OnOpenDirectory( wxCommandEvent& event )
184 {
185  // Get the root directory name:
186  std::vector<TREEPROJECT_ITEM*> tree_data = GetSelectedData();
187 
188  for( TREEPROJECT_ITEM* item_data : tree_data )
189  {
190  // Ask for the new sub directory name
191  wxString curr_dir = item_data->GetDir();
192 
193  if( curr_dir.IsEmpty() )
194  {
195  // Use project path if the tree view path was empty.
196  curr_dir = wxPathOnly( m_Parent->GetProjectFileName() );
197 
198  // As a last resort use the user's documents folder.
199  if( curr_dir.IsEmpty() || !wxFileName::DirExists( curr_dir ) )
200  curr_dir = wxStandardPaths::Get().GetDocumentsDir();
201 
202  if( !curr_dir.IsEmpty() )
203  curr_dir += wxFileName::GetPathSeparator();
204  }
205 
206 #ifdef __WXMAC__
207  wxString msg;
208 
209  // Quote in case there are spaces in the path.
210  msg.Printf( "open \"%s\"", curr_dir );
211 
212  system( msg.c_str() );
213 #else
214  // Quote in case there are spaces in the path.
215  AddDelimiterString( curr_dir );
216 
217  wxLaunchDefaultApplication( curr_dir );
218 #endif
219  }
220 }
221 
222 
223 void TREE_PROJECT_FRAME::OnCreateNewDirectory( wxCommandEvent& event )
224 {
225  // Get the root directory name:
226  std::vector<TREEPROJECT_ITEM*> tree_data = GetSelectedData();
227 
228  for( TREEPROJECT_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() ) // A subdir is selected
236  {
237  // Make this subdir name relative to the current path.
238  // It will be more easy to read by the user, in the next dialog
239  wxFileName fn;
240  fn.AssignDir( curr_dir );
241  fn.MakeRelativeTo( prj_dir );
242  curr_dir = fn.GetPath();
243 
244  if( !curr_dir.IsEmpty() )
245  curr_dir += wxFileName::GetPathSeparator();
246  }
247 
248  wxString msg =
249  wxString::Format( _( "Current project directory:\n%s" ), GetChars( prj_dir ) );
250  wxString subdir = wxGetTextFromUser( msg, _( "Create New Directory" ), curr_dir );
251 
252  if( subdir.IsEmpty() )
253  return;
254 
255  wxString full_dirname = prj_dir + wxFileName::GetPathSeparator() + subdir;
256 
257  // Make the new item and let the file watcher add it to the tree
258  wxMkdir( full_dirname );
259  }
260 }
261 
262 
264 {
265  switch( type )
266  {
267  case TREE_PROJECT: return ProjectFileExtension;
273  case TREE_HTML: return HtmlFileExtension;
274  case TREE_PDF: return PdfFileExtension;
275  case TREE_TXT: return TextFileExtension;
276  case TREE_NET: return NetlistFileExtension;
278  case TREE_REPORT: return ReportFileExtension;
280  case TREE_DRILL: return DrillFileExtension;
281  case TREE_DRILL_NC: return "nc";
282  case TREE_DRILL_XNC: return "xnc";
283  case TREE_SVG: return SVGFileExtension;
287  default: return wxEmptyString;
288  }
289 }
290 
291 
293  const wxString& aName, wxTreeItemId& aRoot, bool aCanResetFileWatcher, bool aRecurse )
294 {
295  wxTreeItemId newItemId;
296  TreeFileType type = TREE_UNKNOWN;
297  wxFileName fn( aName );
298 
299  // Files/dirs names starting by "." are not visible files under unices.
300  // Skip them also under Windows
301  if( fn.GetName().StartsWith( wxT( "." ) ) )
302  return newItemId;
303 
304  if( wxDirExists( aName ) )
305  {
306  type = TREE_DIRECTORY;
307  }
308  else
309  {
310  // Filter
311  wxRegEx reg;
312  bool addFile = false;
313 
314  for( const wxString& m_filter : m_filters )
315  {
316  wxCHECK2_MSG( reg.Compile( m_filter, wxRE_ICASE ), continue,
317  wxString::Format( "Regex %s failed to compile.", m_filter ) );
318 
319  if( reg.Matches( aName ) )
320  {
321  addFile = true;
322  break;
323  }
324  }
325 
326  if( !addFile )
327  return newItemId;
328 
329  // only show the schematic if it is a top level schematic. Eeschema
330  // cannot open a schematic and display it properly unless it starts
331  // at the top of the hierarchy. The schematic is top level only if
332  // there is a line in the header saying:
333  // "Sheet 1 "
334  // However if the file has the same name as the project, it is always
335  // shown, because it is expected the root sheet.
336  // (and to fix an issue (under XP but could exist under other OS),
337  // when a .sch file is created, the file
338  // create is sent to the wxFileSystemWatcher, but the file still has 0 byte
339  // so it cannot detected as root sheet
340  // This is an ugly fix.
341  if( fn.GetExt() == "sch" || fn.GetExt() == "kicad_sch" )
342  {
343  wxString fullFileName = aName.BeforeLast( '.' );
344  wxString rootName;
345  TREEPROJECT_ITEM* itemData = GetItemIdData( m_root );
346 
347  if( itemData )
348  rootName = itemData->GetFileName().BeforeLast( '.' );
349 
350  if( fullFileName != rootName )
351  {
352  char line[128]; // small because we just need a few bytes from the start of a line
353  FILE* fp;
354 
355  fullFileName = aName;
356  fp = wxFopen( fullFileName, wxT( "rt" ) );
357 
358  if( fp == NULL )
359  return newItemId;
360 
361  addFile = false;
362 
363  // check the first 100 lines for the "Sheet 1" or "(page 1" string
364  for( int i = 0; i<100; ++i )
365  {
366  if( !fgets( line, sizeof(line), fp ) )
367  break;
368 
369  if( fn.GetExt() == "sch" )
370  {
371  if( strncmp( line, "Sheet 1 ", 8 ) == 0 )
372  {
373  addFile = true;
374  break;
375  }
376  } else if( fn.GetExt() == "kicad_sch" )
377  {
378  char* start = line;
379 
380  while( *start == ' ' )
381  start++;
382 
383  if( strncmp( start, "(page 1 ", 8 ) == 0 )
384  {
385  addFile = true;
386  break;
387  }
388  }
389  }
390 
391  fclose( fp );
392 
393  if( !addFile )
394  return newItemId; // it is a non-top-level schematic
395  }
396  }
397 
398  for( int i = TREE_PROJECT; i < TREE_MAX; i++ )
399  {
400  wxString ext = GetFileExt( (TreeFileType) i );
401 
402  if( ext == wxT( "" ) )
403  continue;
404 
405  reg.Compile( wxString::FromAscii( "^.*\\" ) + ext +
406  wxString::FromAscii( "$" ), wxRE_ICASE );
407 
408  if( reg.Matches( aName ) )
409  {
410  type = (TreeFileType) i;
411  break;
412  }
413  }
414  }
415 
416  // also check to see if it is already there.
417  wxTreeItemIdValue cookie;
418  wxTreeItemId kid = m_TreeProject->GetFirstChild( aRoot, cookie );
419 
420  while( kid.IsOk() )
421  {
422  TREEPROJECT_ITEM* itemData = GetItemIdData( kid );
423 
424  if( itemData )
425  {
426  if( itemData->GetFileName() == aName )
427  return itemData->GetId(); // well, we would have added it, but it is already here!
428  }
429 
430  kid = m_TreeProject->GetNextChild( aRoot, cookie );
431  }
432 
433  // Append the item (only appending the filename not the full path):
434  wxString file = wxFileNameFromPath( aName );
435  newItemId = m_TreeProject->AppendItem( aRoot, file );
436  TREEPROJECT_ITEM* data = new TREEPROJECT_ITEM( type, aName, m_TreeProject );
437 
438  m_TreeProject->SetItemData( newItemId, data );
439  data->SetState( 0 );
440 
441  // Mark root files (files which have the same aName as the project)
442  wxFileName project( m_Parent->GetProjectFileName() );
443  wxFileName currfile( file );
444 
445  if( currfile.GetName().CmpNoCase( project.GetName() ) == 0 )
446  data->SetRootFile( true );
447  else
448  data->SetRootFile( false );
449 
450 #ifndef __WINDOWS__
451  bool subdir_populated = false;
452 #endif
453 
454  // This section adds dirs and files found in the subdirs
455  // in this case AddFile is recursive, but for the first level only.
456  if( TREE_DIRECTORY == type && aRecurse )
457  {
458  wxDir dir( aName );
459 
460  if( dir.IsOpened() ) // protected dirs will not open properly.
461  {
462  wxString dir_filename;
463 
464  data->SetPopulated( true );
465 #ifndef __WINDOWS__
466  subdir_populated = aCanResetFileWatcher;
467 #endif
468 
469  if( dir.GetFirst( &dir_filename ) )
470  {
471  do // Add name in tree, but do not recurse
472  {
473  wxString path = aName + wxFileName::GetPathSeparator() + dir_filename;
474  AddItemToTreeProject( path, newItemId, false, false );
475  } while( dir.GetNext( &dir_filename ) );
476  }
477  }
478 
479  // Sort filenames by alphabetic order
480  m_TreeProject->SortChildren( newItemId );
481  }
482 
483 #ifndef __WINDOWS__
484  if( subdir_populated )
486 #endif
487 
488  return newItemId;
489 }
490 
491 
493 {
494  wxString pro_dir = m_Parent->GetProjectFileName();
495 
496  if( !m_TreeProject )
497  m_TreeProject = new TREEPROJECTFILES( this );
498  else
499  m_TreeProject->DeleteAllItems();
500 
501  if( !pro_dir ) // This is empty from TREE_PROJECT_FRAME constructor
502  return;
503 
504  wxFileName fn = pro_dir;
505 
506  if( !fn.IsOk() )
507  {
508  fn.Clear();
509  fn.SetPath( wxStandardPaths::Get().GetDocumentsDir() );
510  fn.SetName( NAMELESS_PROJECT );
511  fn.SetExt( ProjectFileExtension );
512  }
513 
514  bool prjOpened = fn.FileExists();
515 
516  // root tree:
517  m_root = m_TreeProject->AddRoot( fn.GetFullName(), TREE_ROOT, TREE_ROOT );
518  m_TreeProject->SetItemBold( m_root, true );
519  m_TreeProject->SetItemData( m_root, new TREEPROJECT_ITEM( TREE_PROJECT, fn.GetFullPath(),
520  m_TreeProject ) );
521 
522  // Now adding all current files if available
523  if( prjOpened )
524  {
525  pro_dir = wxPathOnly( m_Parent->GetProjectFileName() );
526  wxDir dir( pro_dir );
527 
528  if( dir.IsOpened() ) // protected dirs will not open, see "man opendir()"
529  {
530  wxString filename;
531  bool cont = dir.GetFirst( &filename );
532 
533  while( cont )
534  {
535  if( filename != fn.GetFullName() )
536  {
537  wxString name = dir.GetName() + wxFileName::GetPathSeparator() + filename;
538  AddItemToTreeProject( name, m_root, false );
539  }
540 
541  cont = dir.GetNext( &filename );
542  }
543  }
544  }
545  else
546  {
547  m_TreeProject->AppendItem( m_root, wxT( "Empty project" ) );
548  }
549 
550  m_TreeProject->Expand( m_root );
551 
552  // Sort filenames by alphabetic order
553  m_TreeProject->SortChildren( m_root );
554 }
555 
556 
557 void TREE_PROJECT_FRAME::OnRight( wxTreeEvent& Event )
558 {
559  wxTreeItemId curr_item = Event.GetItem();
560 
561  // Ensure item is selected (Under Windows right click does not select the item)
562  m_TreeProject->SelectItem( curr_item );
563 
564  std::vector<TREEPROJECT_ITEM*> tree_data = GetSelectedData();
565 
566  bool can_switch_to_project = true;
567  bool can_create_new_directory = true;
568  bool can_open_this_directory = true;
569  bool can_edit = true;
570  bool can_rename = true;
571  bool can_delete = true;
572  bool can_print = true;
573 
574  if( tree_data.size() == 0 )
575  return;
576 
577  if( tree_data.size() != 1 )
578  {
579  can_switch_to_project = false;
580  can_create_new_directory = false;
581  can_rename = false;
582  can_print = false;
583  }
584 
585  if( curr_item == m_TreeProject->GetRootItem() )
586  can_switch_to_project = false;
587 
588  for( TREEPROJECT_ITEM* item_data : tree_data )
589  {
590  int tree_id = item_data->GetType();
591  wxString full_file_name = item_data->GetFileName();
592 
593  switch( tree_id )
594  {
595  case TREE_PROJECT:
596  can_edit = false;
597  can_rename = false;
598  can_delete = false;
599  can_print = false;
600  break;
601 
602  case TREE_DIRECTORY:
603  can_switch_to_project = false;
604  can_edit = false;
605  can_rename = false;
606  can_print = false;
607  break;
608 
609  default:
610  can_switch_to_project = false;
611  can_create_new_directory = false;
612  can_open_this_directory = false;
613 
614  if( !CanPrintFile( full_file_name ) )
615  can_print = false;
616 
617  break;
618  }
619  }
620 
621  wxMenu popup_menu;
622  wxString text;
623  wxString help_text;
624 
625  if( can_switch_to_project )
626  {
628  _( "Switch to this Project" ),
629  _( "Close all editors, and switch to the selected project" ),
631  popup_menu.AppendSeparator();
632  }
633 
634  if( can_create_new_directory )
635  {
636  AddMenuItem( &popup_menu, ID_PROJECT_NEWDIR, _( "New Directory..." ),
637  _( "Create a New Directory" ), KiBitmap( directory_xpm ) );
638  }
639 
640  if( can_open_this_directory )
641  {
642  if( tree_data.size() == 1 )
643  {
644 #ifdef __APPLE__
645  text = _( "Reveal in Finder" );
646  help_text = _( "Reveals the directory in a Finder window" );
647 #else
648  text = _( "Open Directory in File Explorer" );
649  help_text = _( "Opens the directory in the default system file manager" );
650 #endif
651  }
652  else
653  {
654 #ifdef __APPLE__
655  text = _( "Reveal in Finder" );
656  help_text = _( "Reveals the directories in a Finder window" );
657 #else
658  text = _( "Open Directories in File Explorer" );
659  help_text = _( "Opens the directories in the default system file manager" );
660 #endif
661  }
662 
663  AddMenuItem( &popup_menu, ID_PROJECT_OPEN_DIR, text, help_text,
665  }
666 
667  if( can_edit )
668  {
669  if( tree_data.size() == 1 )
670  help_text = _( "Open the file in a Text Editor" );
671  else
672  help_text = _( "Open files in a Text Editor" );
673 
674  AddMenuItem( &popup_menu, ID_PROJECT_TXTEDIT, _( "Edit in a Text Editor" ),
675  help_text, KiBitmap( editor_xpm ) );
676  }
677 
678  if( can_rename )
679  {
680  if( tree_data.size() == 1 )
681  {
682  text = _( "Rename File..." );
683  help_text = _( "Rename file" );
684  }
685  else
686  {
687  text = _( "Rename Files..." );
688  help_text = _( "Rename files" );
689  }
690 
691  AddMenuItem( &popup_menu, ID_PROJECT_RENAME, text, help_text, KiBitmap( right_xpm ) );
692  }
693 
694  if( can_delete )
695  {
696  if( tree_data.size() == 1 )
697  help_text = _( "Delete the file and its content" );
698  else
699  help_text = _( "Delete the files and their contents" );
700 
701  if( can_switch_to_project || can_create_new_directory || can_open_this_directory || can_edit
702  || can_rename )
703  popup_menu.AppendSeparator();
704 
705  AddMenuItem(
706  &popup_menu, ID_PROJECT_DELETE, _( "Delete" ), help_text, KiBitmap( delete_xpm ) );
707  }
708 
709  if( can_print )
710  {
711  popup_menu.AppendSeparator();
712  AddMenuItem( &popup_menu, ID_PROJECT_PRINT,
713 #ifdef __APPLE__
714  _( "Print..." ),
715 #else
716  _( "Print" ),
717 #endif
718  _( "Print the contents of the file" ), KiBitmap( print_button_xpm ) );
719  }
720 
721  if( popup_menu.GetMenuItemCount() > 0 )
722  PopupMenu( &popup_menu );
723 }
724 
725 
727 {
728  wxString editorname = Pgm().GetEditorName();
729 
730  if( editorname.IsEmpty() )
731  return;
732 
733  std::vector<TREEPROJECT_ITEM*> tree_data = GetSelectedData();
734 
735  wxString files;
736 
737  for( TREEPROJECT_ITEM* item_data : tree_data )
738  {
739  wxString fullFileName = item_data->GetFileName();
740  AddDelimiterString( fullFileName );
741 
742  if( !files.IsEmpty() )
743  files += " ";
744 
745  files += fullFileName;
746  }
747 
748  ExecuteFile( this, editorname, files );
749 }
750 
751 
752 void TREE_PROJECT_FRAME::OnDeleteFile( wxCommandEvent& )
753 {
754  std::vector<TREEPROJECT_ITEM*> tree_data = GetSelectedData();
755  wxString msg, caption;
756 
757  if( tree_data.size() == 1 )
758  {
759  bool is_directory = wxDirExists( tree_data[0]->GetFileName() );
760  msg = wxString::Format(
761  _( "Are you sure you want to delete '%s'?" ), tree_data[0]->GetFileName() );
762  caption = is_directory ? _( "Delete Directory" ) : _( "Delete File" );
763  }
764  else
765  {
766  msg = wxString::Format(
767  _( "Are you sure you want to delete %lu items?" ), tree_data.size() );
768  caption = _( "Delete Multiple Items" );
769  }
770 
771  wxMessageDialog dialog( m_parent, msg, caption, wxYES_NO | wxICON_QUESTION );
772 
773  if( dialog.ShowModal() == wxID_YES )
774  {
775  for( TREEPROJECT_ITEM* item_data : tree_data )
776  item_data->Delete();
777  }
778 }
779 
780 
781 void TREE_PROJECT_FRAME::OnPrintFile( wxCommandEvent& )
782 {
783  std::vector<TREEPROJECT_ITEM*> tree_data = GetSelectedData();
784 
785  for( TREEPROJECT_ITEM* item_data : tree_data )
786  item_data->Print();
787 }
788 
789 
790 void TREE_PROJECT_FRAME::OnRenameFile( wxCommandEvent& )
791 {
792  wxTreeItemId curr_item = m_TreeProject->GetFocusedItem();
793  std::vector<TREEPROJECT_ITEM*> tree_data = GetSelectedData();
794 
795  // XXX: Unnecessary?
796  if( tree_data.size() != 1 )
797  return;
798 
799  wxString buffer = m_TreeProject->GetItemText( curr_item );
800  wxString msg = wxString::Format( _( "Change filename: \"%s\"" ),
801  tree_data[0]->GetFileName() );
802  wxTextEntryDialog dlg( this, msg, _( "Change filename" ), buffer );
803 
804  if( dlg.ShowModal() != wxID_OK )
805  return; // canceled by user
806 
807  buffer = dlg.GetValue();
808  buffer.Trim( true );
809  buffer.Trim( false );
810 
811  if( buffer.IsEmpty() )
812  return; // empty file name not allowed
813 
814  tree_data[0]->Rename( buffer, true );
815  m_isRenaming = true;
816 }
817 
818 
819 void TREE_PROJECT_FRAME::OnSelect( wxTreeEvent& Event )
820 {
821  std::vector<TREEPROJECT_ITEM*> tree_data = GetSelectedData();
822 
823  if( tree_data.size() != 1 )
824  return;
825 
826  tree_data[0]->Activate( this );
827 }
828 
829 
830 void TREE_PROJECT_FRAME::OnExpand( wxTreeEvent& Event )
831 {
832  wxTreeItemId itemId = Event.GetItem();
833  TREEPROJECT_ITEM* tree_data = GetItemIdData( itemId );
834 
835  if( !tree_data )
836  return;
837 
838  if( tree_data->GetType() != TREE_DIRECTORY )
839  return;
840 
841  // explore list of non populated subdirs, and populate them
842  wxTreeItemIdValue cookie;
843  wxTreeItemId kid = m_TreeProject->GetFirstChild( itemId, cookie );
844 
845 #ifndef __WINDOWS__
846  bool subdir_populated = false;
847 #endif
848 
849  for( ; kid.IsOk(); kid = m_TreeProject->GetNextChild( itemId, cookie ) )
850  {
851  TREEPROJECT_ITEM* itemData = GetItemIdData( kid );
852 
853  if( !itemData || itemData->GetType() != TREE_DIRECTORY )
854  continue;
855 
856  if( itemData->IsPopulated() )
857  continue;
858 
859  wxString fileName = itemData->GetFileName();
860  wxDir dir( fileName );
861 
862  if( dir.IsOpened() )
863  {
864  wxString dir_filename;
865 
866  if( dir.GetFirst( &dir_filename ) )
867  {
868  do // Add name to tree item, but do not recurse in subdirs:
869  {
870  wxString name = fileName + wxFileName::GetPathSeparator() + dir_filename;
871  AddItemToTreeProject( name, kid, false );
872  } while( dir.GetNext( &dir_filename ) );
873  }
874 
875  itemData->SetPopulated( true ); // set state to populated
876 #ifndef __WINDOWS__
877  subdir_populated = true;
878 #endif
879  }
880 
881  // Sort filenames by alphabetic order
882  m_TreeProject->SortChildren( kid );
883  }
884 
885 #ifndef __WINDOWS__
886  if( subdir_populated )
888 #endif
889 }
890 
891 
892 std::vector<TREEPROJECT_ITEM*> TREE_PROJECT_FRAME::GetSelectedData()
893 {
894  wxArrayTreeItemIds selection;
895  std::vector<TREEPROJECT_ITEM*> data;
896 
897  m_TreeProject->GetSelections( selection );
898 
899  for( auto it = selection.begin(); it != selection.end(); it++ )
900  data.push_back( GetItemIdData( *it ) );
901 
902  return data;
903 }
904 
905 
907 {
908  return dynamic_cast<TREEPROJECT_ITEM*>( m_TreeProject->GetItemData( aId ) );
909 }
910 
911 
912 wxTreeItemId TREE_PROJECT_FRAME::findSubdirTreeItem( const wxString& aSubDir )
913 {
914  wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
915 
916  // If the subdir is the current working directory, return m_root
917  // in main list:
918  if( prj_dir == aSubDir )
919  return m_root;
920 
921  // The subdir is in the main tree or in a subdir: Locate it
922  wxTreeItemIdValue cookie;
923  wxTreeItemId root_id = m_root;
924  std::stack < wxTreeItemId > subdirs_id;
925 
926  wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
927 
928  while( true )
929  {
930  if( ! kid.IsOk() )
931  {
932  if( subdirs_id.empty() ) // all items were explored
933  {
934  root_id = kid; // Not found: return an invalid wxTreeItemId
935  break;
936  }
937  else
938  {
939  root_id = subdirs_id.top();
940  subdirs_id.pop();
941  kid = m_TreeProject->GetFirstChild( root_id, cookie );
942 
943  if( ! kid.IsOk() )
944  continue;
945  }
946  }
947 
948  TREEPROJECT_ITEM* itemData = GetItemIdData( kid );
949 
950  if( itemData && ( itemData->GetType() == TREE_DIRECTORY ) )
951  {
952  if( itemData->GetFileName() == aSubDir ) // Found!
953  {
954  root_id = kid;
955  break;
956  }
957 
958  // kid is a subdir, push in list to explore it later
959  if( itemData->IsPopulated() )
960  subdirs_id.push( kid );
961  }
962 
963  kid = m_TreeProject->GetNextChild( root_id, cookie );
964  }
965 
966  return root_id;
967 }
968 
969 
970 void TREE_PROJECT_FRAME::OnFileSystemEvent( wxFileSystemWatcherEvent& event )
971 {
972  const wxFileName& pathModified = event.GetPath();
973  wxString subdir = pathModified.GetPath();
974  wxString fn = pathModified.GetFullPath();
975 
976  switch( event.GetChangeType() )
977  {
978  case wxFSW_EVENT_DELETE:
979  case wxFSW_EVENT_CREATE:
980  case wxFSW_EVENT_RENAME:
981  break;
982 
983  case wxFSW_EVENT_MODIFY:
984  case wxFSW_EVENT_ACCESS:
985  default:
986  return;
987  }
988 
989  wxTreeItemId root_id = findSubdirTreeItem( subdir );
990 
991  if( !root_id.IsOk() )
992  return;
993 
994  wxTreeItemIdValue cookie; // dummy variable needed by GetFirstChild()
995  wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
996 
997  switch( event.GetChangeType() )
998  {
999  case wxFSW_EVENT_CREATE:
1000  {
1001  wxTreeItemId newitem = AddItemToTreeProject( pathModified.GetFullPath(), root_id );
1002 
1003  // If we are in the process of renaming a file, select the new one
1004  // This is needed for MSW and OSX, since we don't get RENAME events from them, just a
1005  // pair of DELETE and CREATE events.
1006  if( m_isRenaming && newitem.IsOk() )
1007  {
1008  m_TreeProject->SelectItem( newitem );
1009  m_isRenaming = false;
1010  }
1011  }
1012  break;
1013 
1014  case wxFSW_EVENT_DELETE:
1015  while( kid.IsOk() )
1016  {
1017  TREEPROJECT_ITEM* itemData = GetItemIdData( kid );
1018 
1019  if( itemData && itemData->GetFileName() == fn )
1020  {
1021  m_TreeProject->Delete( kid );
1022  return;
1023  }
1024  kid = m_TreeProject->GetNextChild( root_id, cookie );
1025  }
1026  break;
1027 
1028  case wxFSW_EVENT_RENAME :
1029  {
1030  const wxFileName& newpath = event.GetNewPath();
1031  wxString newdir = newpath.GetPath();
1032  wxString newfn = newpath.GetFullPath();
1033 
1034  while( kid.IsOk() )
1035  {
1036  TREEPROJECT_ITEM* itemData = GetItemIdData( kid );
1037 
1038  if( itemData && itemData->GetFileName() == fn )
1039  {
1040  m_TreeProject->Delete( kid );
1041  break;
1042  }
1043 
1044  kid = m_TreeProject->GetNextChild( root_id, cookie );
1045  }
1046 
1047  // Add the new item only if it is not the current project file (root item).
1048  // Remember: this code is called by a wxFileSystemWatcherEvent event, and not always
1049  // called after an actual file rename, and the cleanup code does not explore the
1050  // root item, because it cannot be renamed by the user.
1051  TREEPROJECT_ITEM* rootData = GetItemIdData( root_id );
1052 
1053  if( newfn != rootData->GetFileName() )
1054  {
1055  wxTreeItemId newroot_id = findSubdirTreeItem( newdir );
1056  wxTreeItemId newitem = AddItemToTreeProject( newfn, newroot_id );
1057 
1058  // If the item exists, select it
1059  if( newitem.IsOk() )
1060  m_TreeProject->SelectItem( newitem );
1061  }
1062 
1063  m_isRenaming = false;
1064  }
1065  break;
1066  }
1067 
1068  // Sort filenames by alphabetic order
1069  m_TreeProject->SortChildren( root_id );
1070 }
1071 
1072 
1074 {
1075  // Prepare file watcher:
1076  if( m_watcher )
1077  {
1078  m_watcher->RemoveAll();
1079  }
1080  else
1081  {
1082  m_watcher = new wxFileSystemWatcher();
1083  m_watcher->SetOwner( this );
1084  }
1085 
1086  // We can see wxString under a debugger, not a wxFileName
1087  wxString prj_dir = wxPathOnly( m_Parent->GetProjectFileName() );
1088  wxFileName fn;
1089  fn.AssignDir( prj_dir );
1090  fn.DontFollowLink();
1091 
1092  // Add directories which should be monitored.
1093  // under windows, we add the curr dir and all subdirs
1094  // under unix, we add only the curr dir and the populated subdirs
1095  // see http://docs.wxwidgets.org/trunk/classwx_file_system_watcher.htm
1096  // under unix, the file watcher needs more work to be efficient
1097  // moreover, under wxWidgets 2.9.4, AddTree does not work properly.
1098 #ifdef __WINDOWS__
1099  m_watcher->AddTree( fn );
1100 #else
1101  m_watcher->Add( fn );
1102 
1103  // Add subdirs
1104  wxTreeItemIdValue cookie;
1105  wxTreeItemId root_id = m_root;
1106 
1107  std::stack < wxTreeItemId > subdirs_id;
1108 
1109  wxTreeItemId kid = m_TreeProject->GetFirstChild( root_id, cookie );
1110 
1111  while( true )
1112  {
1113  if( !kid.IsOk() )
1114  {
1115  if( subdirs_id.empty() ) // all items were explored
1116  break;
1117  else
1118  {
1119  root_id = subdirs_id.top();
1120  subdirs_id.pop();
1121  kid = m_TreeProject->GetFirstChild( root_id, cookie );
1122 
1123  if( !kid.IsOk() )
1124  continue;
1125  }
1126  }
1127 
1128  TREEPROJECT_ITEM* itemData = GetItemIdData( kid );
1129 
1130  if( itemData && itemData->GetType() == TREE_DIRECTORY )
1131  {
1132  // we can see wxString under a debugger, not a wxFileName
1133  const wxString& path = itemData->GetFileName();
1134 
1135  wxLogTrace( tracePathsAndFiles, "%s: add '%s'\n", __func__, TO_UTF8( path ) );
1136 
1137  if( wxFileName::IsDirReadable( path ) ) // linux whines about watching protected dir
1138  {
1139  fn.AssignDir( path );
1140  m_watcher->Add( fn );
1141 
1142  // if kid is a subdir, push in list to explore it later
1143  if( itemData->IsPopulated() && m_TreeProject->GetChildrenCount( kid ) )
1144  subdirs_id.push( kid );
1145  }
1146  }
1147 
1148  kid = m_TreeProject->GetNextChild( root_id, cookie );
1149  }
1150 #endif
1151 
1152 #if defined(DEBUG) && 1
1153  wxArrayString paths;
1154  m_watcher->GetWatchedPaths( &paths );
1155  wxLogTrace( tracePathsAndFiles, "%s: watched paths:", __func__ );
1156 
1157  for( unsigned ii = 0; ii < paths.GetCount(); ii++ )
1158  wxLogTrace( tracePathsAndFiles, " %s\n", TO_UTF8( paths[ii] ) );
1159 #endif
1160 }
1161 
1162 
1163 void KICAD_MANAGER_FRAME::OnChangeWatchedPaths( wxCommandEvent& aEvent )
1164 {
1166 }
const std::string NetlistFileExtension
void OnOpenDirectory(wxCommandEvent &event)
Function OnOpenDirectory Handles the right-click menu for opening a directory in the current system f...
std::vector< TREEPROJECT_ITEM * > GetSelectedData()
Function GetSelectedData return the item data from item currently selected (highlighted) Note this is...
TREEPROJECT_ITEM handles one item (a file or a directory name) for the tree file.
IDs used in KiCad main frame foe menuitems and tools.
const BITMAP_OPAQUE right_xpm[1]
Definition: right.cpp:49
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:104
const wxString GetProjectFileName() const
This file is part of the common library TODO brief description.
const std::string KiCadFootprintFileExtension
const std::string ProjectFileExtension
const std::string LegacyPcbFileExtension
const std::string LegacySymbolLibFileExtension
const BITMAP_OPAQUE editor_xpm[1]
Definition: editor.cpp:83
wxMenuItem * AddMenuItem(wxMenu *aMenu, int aId, const wxString &aText, const wxBitmap &aImage, wxItemKind aType=wxITEM_NORMAL)
Function AddMenuItem is an inline helper function to create and insert a menu item with an icon into ...
Definition: bitmap.cpp:250
TREEPROJECTFILES This is the class to show (as a tree) the files in the project directory.
const wxChar *const tracePathsAndFiles
Flag to enable path and file name debug output.
const BITMAP_OPAQUE directory_browser_xpm[1]
TreeFileType GetType() const
const std::string ComponentFileExtension
const std::string KiCadPcbFileExtension
void SetRootFile(bool aValue)
TREEPROJECTFILES * m_TreeProject
wxTreeItemId findSubdirTreeItem(const wxString &aSubDir)
Function findSubdirTreeItem searches for the item in tree project which is the node of the subdirecto...
const wxChar TextFileExtension[]
const std::string HtmlFileExtension
wxBitmap KiBitmap(BITMAP_DEF aBitmap)
Construct a wxBitmap from a memory record, held in a BITMAP_DEF.
Definition: bitmap.cpp:80
#define NAMELESS_PROJECT
void OnSelect(wxTreeEvent &Event)
Called on a double click on an item.
bool IsPopulated() const
void OnSwitchToSelectedProject(wxCommandEvent &event)
Switch to a other project selected from the tree project (by selecting an other .pro file inside the ...
const wxString GerberFileExtensionWildCard(".((gbr|gbrjob|(gb|gt)[alops])|pho)")
void OnFileSystemEvent(wxFileSystemWatcherEvent &event)
called when a file or directory is modified/created/deleted The tree project is modified when a file ...
#define NULL
TREE_PROJECT_FRAME Window to display the tree files.
TreeFileType
friend class TREEPROJECT_ITEM
void OnPrintFile(wxCommandEvent &event)
Function OnDeleteFile Print the selected file or directory in the tree project.
KICAD_MANAGER_FRAME * m_Parent
void OnDeleteFile(wxCommandEvent &event)
Function OnDeleteFile Delete the selected file or directory in the tree project.
Definition of file extensions used in Kicad.
void OnChangeWatchedPaths(wxCommandEvent &aEvent)
Called by sending a event with id = ID_INIT_WATCHED_PATHS rebuild the list of wahtched paths.
void ReCreateTreePrj()
Create or modify the tree showing project file names.
wxLogTrace helper definitions.
void AddDelimiterString(wxString &string)
Function AddDelimiterString Add un " to the start and the end of string (if not already done).
Definition: gestfich.cpp:42
void OnOpenSelectedFileWithTextEditor(wxCommandEvent &event)
Function OnOpenSelectedFileWithTextEditor Call the text editor to open the selected file in the tree ...
const BITMAP_OPAQUE open_project_xpm[1]
const std::string PdfFileExtension
void OnRight(wxTreeEvent &Event)
Called on a right click on an item.
const std::string LegacySchematicFileExtension
void OnRenameFile(wxCommandEvent &event)
Function OnRenameFile Rename the selected file or directory in the tree project.
const std::string PageLayoutDescrFileExtension
const BITMAP_OPAQUE directory_xpm[1]
Definition: directory.cpp:30
void FileWatcherReset()
Reinit the watched paths Should be called after opening a new project to rebuild the list of watched ...
TREEPROJECT_ITEM * GetItemIdData(wxTreeItemId aId)
Function GetItemIdData return the item data corresponding to a wxTreeItemId identifier.
static const wxChar * GetChars(const wxString &s)
Function GetChars returns a wxChar* to the actual wxChar* data within a wxString, and is helpful for ...
Definition: macros.h:153
const char * name
Definition: DXF_plotter.cpp:60
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:205
void SetState(int state)
wxFileSystemWatcher * m_watcher
const std::string ReportFileExtension
#define _(s)
Definition: 3d_actions.cpp:33
wxTreeItemId AddItemToTreeProject(const wxString &aName, wxTreeItemId &aRoot, bool aCanResetFileWatcher=true, bool aRecurse=true)
Function AddItemToTreeProject.
const std::string KiCadSchematicFileExtension
const std::string SVGFileExtension
void OnExpand(wxTreeEvent &Event)
Called on a click on the + or - button of an item with children.
#define TO_UTF8(wxstring)
const std::string FootprintPlaceFileExtension
void LoadProject(const wxFileName &aProjectFileName)
const wxString & GetFileName() const
std::vector< wxString > m_filters
static wxString GetFileExt(TreeFileType type)
int ExecuteFile(wxWindow *frame, const wxString &ExecFile, const wxString &param, wxProcess *callback)
Function ExecuteFile calls the executable file ExecFile with the command line parameters param.
Definition: gestfich.cpp:174
void OnCreateNewDirectory(wxCommandEvent &event)
Function OnCreateNewDirectory Creates a new subdirectory inside the current kicad project directory t...
static const wxChar * s_allowedExtensionsToList[]
TREE_PROJECT_FRAME * m_leftWin
The main KiCad project manager frame.
bool CanPrintFile(const wxString &file)
Definition: gestfich.cpp:357
void SetPopulated(bool aValue)
const std::string DrillFileExtension
const BITMAP_OPAQUE delete_xpm[1]
Definition: delete.cpp:62