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