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