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