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