You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1089 lines
29 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2014-2020 Jean-Pierre Charras, jp.charras at wanadoo.fr
  5. * Copyright (C) 2008 Wayne Stambaugh <stambaughw@gmail.com>
  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. #include <eda_base_frame.h>
  26. #include <project.h>
  27. #include <common.h>
  28. #include <reporter.h>
  29. #include <macros.h>
  30. #include <mutex>
  31. #include <wx/process.h>
  32. #include <wx/config.h>
  33. #include <wx/utils.h>
  34. #include <wx/stdpaths.h>
  35. #include <wx/url.h>
  36. #include <boost/uuid/uuid_generators.hpp>
  37. #include <boost/uuid/uuid_io.hpp>
  38. #include <boost/functional/hash.hpp>
  39. // Create only once, as seeding is *very* expensive
  40. static boost::uuids::random_generator randomGenerator;
  41. // These don't have the same performance penalty, but might as well be consistent
  42. static boost::uuids::string_generator stringGenerator;
  43. static boost::uuids::nil_generator nilGenerator;
  44. // Global nil reference
  45. KIID niluuid( 0 );
  46. // For static initialization
  47. KIID& NilUuid()
  48. {
  49. static KIID nil( 0 );
  50. return nil;
  51. }
  52. KIID::KIID() :
  53. m_uuid( randomGenerator() ),
  54. m_cached_timestamp( 0 )
  55. {
  56. }
  57. KIID::KIID( int null ) :
  58. m_uuid( nilGenerator() ),
  59. m_cached_timestamp( 0 )
  60. {
  61. wxASSERT( null == 0 );
  62. }
  63. KIID::KIID( const wxString& aString ) :
  64. m_uuid(),
  65. m_cached_timestamp( 0 )
  66. {
  67. if( aString.length() == 8 )
  68. {
  69. // A legacy-timestamp-based UUID has only the last 4 octets filled in.
  70. // Convert them individually to avoid stepping in the little-endian/big-endian
  71. // doo-doo.
  72. for( int i = 0; i < 4; ++i )
  73. {
  74. wxString octet = aString.substr( i * 2, 2 );
  75. m_uuid.data[ i + 12 ] = strtol( octet.data(), NULL, 16 );
  76. }
  77. m_cached_timestamp = strtol( aString.c_str(), NULL, 16 );
  78. }
  79. else
  80. {
  81. try
  82. {
  83. m_uuid = stringGenerator( aString.wc_str() );
  84. if( IsLegacyTimestamp() )
  85. m_cached_timestamp = strtol( aString.substr( 28 ).c_str(), NULL, 16 );
  86. }
  87. catch( ... )
  88. {
  89. // Failed to parse string representation; best we can do is assign a new
  90. // random one.
  91. m_uuid = randomGenerator();
  92. }
  93. }
  94. }
  95. KIID::KIID( timestamp_t aTimestamp )
  96. {
  97. m_cached_timestamp = aTimestamp;
  98. // A legacy-timestamp-based UUID has only the last 4 octets filled in.
  99. // Convert them individually to avoid stepping in the little-endian/big-endian
  100. // doo-doo.
  101. wxString str = AsLegacyTimestampString();
  102. for( int i = 0; i < 4; ++i )
  103. {
  104. wxString octet = str.substr( i * 2, 2 );
  105. m_uuid.data[ i + 12 ] = strtol( octet.data(), NULL, 16 );
  106. }
  107. }
  108. bool KIID::IsLegacyTimestamp() const
  109. {
  110. return !m_uuid.data[8] && !m_uuid.data[9] && !m_uuid.data[10] && !m_uuid.data[11];
  111. }
  112. timestamp_t KIID::AsLegacyTimestamp() const
  113. {
  114. return m_cached_timestamp;
  115. }
  116. size_t KIID::Hash() const
  117. {
  118. size_t hash = 0;
  119. // Note: this is NOT little-endian/big-endian safe, but as long as it's just used
  120. // at runtime it won't matter.
  121. for( int i = 0; i < 4; ++i )
  122. boost::hash_combine( hash, reinterpret_cast<const uint32_t*>( m_uuid.data )[i] );
  123. return hash;
  124. }
  125. void KIID::Clone( const KIID& aUUID )
  126. {
  127. m_uuid = aUUID.m_uuid;
  128. m_cached_timestamp = aUUID.m_cached_timestamp;
  129. }
  130. wxString KIID::AsString() const
  131. {
  132. return boost::uuids::to_string( m_uuid );
  133. }
  134. wxString KIID::AsLegacyTimestampString() const
  135. {
  136. return wxString::Format( "%8.8lX", (unsigned long) AsLegacyTimestamp() );
  137. }
  138. void KIID::ConvertTimestampToUuid()
  139. {
  140. if( !IsLegacyTimestamp() )
  141. return;
  142. m_cached_timestamp = 0;
  143. m_uuid = randomGenerator();
  144. }
  145. bool IsImperialUnit( EDA_UNITS aUnit )
  146. {
  147. switch( aUnit )
  148. {
  149. case EDA_UNITS::INCHES:
  150. case EDA_UNITS::MILS:
  151. return true;
  152. default:
  153. return false;
  154. }
  155. return false;
  156. }
  157. bool IsMetricUnit( EDA_UNITS aUnit )
  158. {
  159. switch( aUnit )
  160. {
  161. case EDA_UNITS::MILLIMETRES:
  162. return true;
  163. default:
  164. return false;
  165. }
  166. return false;
  167. }
  168. /**
  169. * Global variables definitions.
  170. *
  171. * TODO: All of these variables should be moved into the class were they
  172. * are defined and used. Most of them probably belong in the
  173. * application class.
  174. */
  175. // When reading/writing files, we need to swtich to setlocale( LC_NUMERIC, "C" ).
  176. // Works fine to read/write files with floating point numbers.
  177. // We can call setlocale( LC_NUMERIC, "C" ) of wxLocale( "C", "C", "C", false )
  178. // wxWidgets discourage a direct call to setlocale
  179. // However, for us, calling wxLocale( "C", "C", "C", false ) has a unwanted effect:
  180. // The I18N translations are no longer active, because the English dixtionary is selected.
  181. // To read files, this is not a major issues, but the resul can differ
  182. // from using setlocale(xx, "C").
  183. // Previouly, we called setlocale( LC_NUMERIC, "C" )
  184. // The old code will be removed when calling wxLocale( "C", "C", "C", false )
  185. // is fully tested, and all issues fixed
  186. #define USE_WXLOCALE 1 /* 0 to call setlocale, 1 to call wxLocale */
  187. // On Windows, when using setlocale, a wx alert is generated
  188. // in some cases (reading a bitmap for instance)
  189. // So we disable alerts during the time a file is read or written
  190. #if !USE_WXLOCALE
  191. #if defined( _WIN32 ) && defined( DEBUG )
  192. // a wxAssertHandler_t function to filter wxWidgets alert messages when reading/writing a file
  193. // when switching the locale to LC_NUMERIC, "C"
  194. // It is used in class LOCALE_IO to hide a useless (in kicad) wxWidgets alert message
  195. void KiAssertFilter( const wxString &file, int line,
  196. const wxString &func, const wxString &cond,
  197. const wxString &msg)
  198. {
  199. if( !msg.Contains( "Decimal separator mismatch" ) )
  200. wxTheApp->OnAssertFailure( file.c_str(), line, func.c_str(), cond.c_str(), msg.c_str() );
  201. }
  202. #endif
  203. #endif
  204. std::atomic<unsigned int> LOCALE_IO::m_c_count( 0 );
  205. LOCALE_IO::LOCALE_IO() : m_wxLocale( nullptr )
  206. {
  207. // use thread safe, atomic operation
  208. if( m_c_count++ == 0 )
  209. {
  210. #if USE_WXLOCALE
  211. m_wxLocale = new wxLocale( "C", "C", "C", false );
  212. #else
  213. // Store the user locale name, to restore this locale later, in dtor
  214. m_user_locale = setlocale( LC_NUMERIC, nullptr );
  215. #if defined( _WIN32 ) && defined( DEBUG )
  216. // Disable wxWidgets alerts
  217. wxSetAssertHandler( KiAssertFilter );
  218. #endif
  219. // Switch the locale to C locale, to read/write files with fp numbers
  220. setlocale( LC_NUMERIC, "C" );
  221. #endif
  222. }
  223. }
  224. LOCALE_IO::~LOCALE_IO()
  225. {
  226. // use thread safe, atomic operation
  227. if( --m_c_count == 0 )
  228. {
  229. // revert to the user locale
  230. #if USE_WXLOCALE
  231. delete m_wxLocale; // Deleting m_wxLocale restored previous locale
  232. m_wxLocale = nullptr;
  233. #else
  234. setlocale( LC_NUMERIC, m_user_locale.c_str() );
  235. #if defined( _WIN32 ) && defined( DEBUG )
  236. // Enable wxWidgets alerts
  237. wxSetDefaultAssertHandler();
  238. #endif
  239. #endif
  240. }
  241. }
  242. wxSize GetTextSize( const wxString& aSingleLine, wxWindow* aWindow )
  243. {
  244. wxCoord width;
  245. wxCoord height;
  246. {
  247. wxClientDC dc( aWindow );
  248. dc.SetFont( aWindow->GetFont() );
  249. dc.GetTextExtent( aSingleLine, &width, &height );
  250. }
  251. return wxSize( width, height );
  252. }
  253. bool EnsureTextCtrlWidth( wxTextCtrl* aCtrl, const wxString* aString )
  254. {
  255. wxWindow* window = aCtrl->GetParent();
  256. if( !window )
  257. window = aCtrl;
  258. wxString ctrlText;
  259. if( !aString )
  260. {
  261. ctrlText = aCtrl->GetValue();
  262. aString = &ctrlText;
  263. }
  264. wxSize textz = GetTextSize( *aString, window );
  265. wxSize ctrlz = aCtrl->GetSize();
  266. if( ctrlz.GetWidth() < textz.GetWidth() + 10 )
  267. {
  268. ctrlz.SetWidth( textz.GetWidth() + 10 );
  269. aCtrl->SetSizeHints( ctrlz );
  270. return true;
  271. }
  272. return false;
  273. }
  274. void SelectReferenceNumber( wxTextEntry* aTextEntry )
  275. {
  276. wxString ref = aTextEntry->GetValue();
  277. if( ref.find_first_of( '?' ) != ref.npos )
  278. {
  279. aTextEntry->SetSelection( ref.find_first_of( '?' ), ref.find_last_of( '?' ) + 1 );
  280. }
  281. else
  282. {
  283. wxString num = ref;
  284. while( !num.IsEmpty() && ( !isdigit( num.Last() ) || !isdigit( num.GetChar( 0 ) ) ) )
  285. {
  286. // Trim non-digit from end
  287. if( !isdigit( num.Last() ) )
  288. num.RemoveLast();
  289. // Trim non-digit from the start
  290. if( !num.IsEmpty() && !isdigit( num.GetChar( 0 ) ) )
  291. num = num.Right( num.Length() - 1 );
  292. }
  293. aTextEntry->SetSelection( ref.Find( num ), ref.Find( num ) + num.Length() );
  294. if( num.IsEmpty() )
  295. aTextEntry->SetSelection( -1, -1 );
  296. }
  297. }
  298. void wxStringSplit( const wxString& aText, wxArrayString& aStrings, wxChar aSplitter )
  299. {
  300. wxString tmp;
  301. for( unsigned ii = 0; ii < aText.Length(); ii++ )
  302. {
  303. if( aText[ii] == aSplitter )
  304. {
  305. aStrings.Add( tmp );
  306. tmp.Clear();
  307. }
  308. else
  309. tmp << aText[ii];
  310. }
  311. if( !tmp.IsEmpty() )
  312. {
  313. aStrings.Add( tmp );
  314. }
  315. }
  316. int ProcessExecute( const wxString& aCommandLine, int aFlags, wxProcess *callback )
  317. {
  318. return (int) wxExecute( aCommandLine, aFlags, callback );
  319. }
  320. enum Bracket
  321. {
  322. Bracket_None,
  323. Bracket_Normal = ')',
  324. Bracket_Curly = '}',
  325. #ifdef __WINDOWS__
  326. Bracket_Windows = '%', // yeah, Windows people are a bit strange ;-)
  327. #endif
  328. Bracket_Max
  329. };
  330. wxString ExpandTextVars( const wxString& aSource,
  331. const std::function<bool( wxString* )>* aLocalResolver,
  332. const PROJECT* aProject,
  333. const std::function<bool( wxString* )>* aFallbackResolver )
  334. {
  335. wxString newbuf;
  336. size_t sourceLen = aSource.length();
  337. newbuf.Alloc( sourceLen ); // best guess (improves performance)
  338. for( size_t i = 0; i < sourceLen; ++i )
  339. {
  340. if( aSource[i] == '$' && i + 1 < sourceLen && aSource[i+1] == '{' )
  341. {
  342. wxString token;
  343. for( i = i + 2; i < sourceLen; ++i )
  344. {
  345. if( aSource[i] == '}' )
  346. break;
  347. else
  348. token.append( aSource[i] );
  349. }
  350. if( token.IsEmpty() )
  351. continue;
  352. if( aLocalResolver && (*aLocalResolver)( &token ) )
  353. {
  354. newbuf.append( token );
  355. }
  356. else if( aProject && aProject->TextVarResolver( &token ) )
  357. {
  358. newbuf.append( token );
  359. }
  360. else if( aFallbackResolver && (*aFallbackResolver)( &token ) )
  361. {
  362. newbuf.append( token );
  363. }
  364. else
  365. {
  366. // Token not resolved: leave the reference unchanged
  367. newbuf.append( "${" + token + "}" );
  368. }
  369. }
  370. else
  371. {
  372. newbuf.append( aSource[i] );
  373. }
  374. }
  375. return newbuf;
  376. }
  377. //
  378. // Stolen from wxExpandEnvVars and then heavily optimized
  379. //
  380. wxString KIwxExpandEnvVars( const wxString& str, const PROJECT* aProject )
  381. {
  382. size_t strlen = str.length();
  383. wxString strResult;
  384. strResult.Alloc( strlen ); // best guess (improves performance)
  385. for( size_t n = 0; n < strlen; n++ )
  386. {
  387. wxUniChar str_n = str[n];
  388. switch( str_n.GetValue() )
  389. {
  390. #ifdef __WINDOWS__
  391. case wxT( '%' ):
  392. #endif // __WINDOWS__
  393. case wxT( '$' ):
  394. {
  395. Bracket bracket;
  396. #ifdef __WINDOWS__
  397. if( str_n == wxT( '%' ) )
  398. bracket = Bracket_Windows;
  399. else
  400. #endif // __WINDOWS__
  401. if( n == strlen - 1 )
  402. {
  403. bracket = Bracket_None;
  404. }
  405. else
  406. {
  407. switch( str[n + 1].GetValue() )
  408. {
  409. case wxT( '(' ):
  410. bracket = Bracket_Normal;
  411. str_n = str[++n]; // skip the bracket
  412. break;
  413. case wxT( '{' ):
  414. bracket = Bracket_Curly;
  415. str_n = str[++n]; // skip the bracket
  416. break;
  417. default:
  418. bracket = Bracket_None;
  419. }
  420. }
  421. size_t m = n + 1;
  422. wxUniChar str_m = str[m];
  423. while( m < strlen && ( wxIsalnum( str_m ) || str_m == wxT( '_' ) || str_m == wxT( ':' ) ) )
  424. str_m = str[++m];
  425. wxString strVarName( str.c_str() + n + 1, m - n - 1 );
  426. // NB: use wxGetEnv instead of wxGetenv as otherwise variables
  427. // set through wxSetEnv may not be read correctly!
  428. bool expanded = false;
  429. wxString tmp = strVarName;
  430. if( aProject && aProject->TextVarResolver( &tmp ) )
  431. {
  432. strResult += tmp;
  433. expanded = true;
  434. }
  435. else if( wxGetEnv( strVarName, &tmp ) )
  436. {
  437. strResult += tmp;
  438. expanded = true;
  439. }
  440. else
  441. {
  442. // variable doesn't exist => don't change anything
  443. #ifdef __WINDOWS__
  444. if ( bracket != Bracket_Windows )
  445. #endif
  446. if ( bracket != Bracket_None )
  447. strResult << str[n - 1];
  448. strResult << str_n << strVarName;
  449. }
  450. // check the closing bracket
  451. if( bracket != Bracket_None )
  452. {
  453. if( m == strlen || str_m != (wxChar)bracket )
  454. {
  455. // under MSW it's common to have '%' characters in the registry
  456. // and it's annoying to have warnings about them each time, so
  457. // ignore them silently if they are not used for env vars
  458. //
  459. // under Unix, OTOH, this warning could be useful for the user to
  460. // understand why isn't the variable expanded as intended
  461. #ifndef __WINDOWS__
  462. wxLogWarning( _( "Environment variables expansion failed: missing '%c' "
  463. "at position %u in '%s'." ),
  464. (char)bracket, (unsigned int) (m + 1), str.c_str() );
  465. #endif // __WINDOWS__
  466. }
  467. else
  468. {
  469. // skip closing bracket unless the variables wasn't expanded
  470. if( !expanded )
  471. strResult << (wxChar)bracket;
  472. m++;
  473. }
  474. }
  475. n = m - 1; // skip variable name
  476. str_n = str[n];
  477. }
  478. break;
  479. case wxT( '\\' ):
  480. // backslash can be used to suppress special meaning of % and $
  481. if( n != strlen - 1 && (str[n + 1] == wxT( '%' ) || str[n + 1] == wxT( '$' )) )
  482. {
  483. str_n = str[++n];
  484. strResult += str_n;
  485. break;
  486. }
  487. KI_FALLTHROUGH;
  488. default:
  489. strResult += str_n;
  490. }
  491. }
  492. #ifndef __WINDOWS__
  493. if( strResult.StartsWith( "~" ) )
  494. strResult.Replace( "~", wxGetHomeDir(), false );
  495. #endif // __WINDOWS__
  496. return strResult;
  497. }
  498. const wxString ExpandEnvVarSubstitutions( const wxString& aString, PROJECT* aProject )
  499. {
  500. // wxGetenv( wchar_t* ) is not re-entrant on linux.
  501. // Put a lock on multithreaded use of wxGetenv( wchar_t* ), called from wxEpandEnvVars(),
  502. static std::mutex getenv_mutex;
  503. std::lock_guard<std::mutex> lock( getenv_mutex );
  504. // We reserve the right to do this another way, by providing our own member function.
  505. return KIwxExpandEnvVars( aString, aProject );
  506. }
  507. const wxString ResolveUriByEnvVars( const wxString& aUri, PROJECT* aProject )
  508. {
  509. wxString uri = ExpandTextVars( aUri, nullptr, aProject );
  510. // URL-like URI: return as is.
  511. wxURL url( uri );
  512. if( url.GetError() == wxURL_NOERR )
  513. return uri;
  514. // Otherwise, the path points to a local file. Resolve environment variables if any.
  515. return ExpandEnvVarSubstitutions( aUri, aProject );
  516. }
  517. bool EnsureFileDirectoryExists( wxFileName* aTargetFullFileName,
  518. const wxString& aBaseFilename,
  519. REPORTER* aReporter )
  520. {
  521. wxString msg;
  522. wxString baseFilePath = wxFileName( aBaseFilename ).GetPath();
  523. // make aTargetFullFileName path, which is relative to aBaseFilename path (if it is not
  524. // already an absolute path) absolute:
  525. if( !aTargetFullFileName->MakeAbsolute( baseFilePath ) )
  526. {
  527. if( aReporter )
  528. {
  529. msg.Printf( _( "Cannot make path \"%s\" absolute with respect to \"%s\"." ),
  530. aTargetFullFileName->GetPath(),
  531. baseFilePath );
  532. aReporter->Report( msg, RPT_SEVERITY_ERROR );
  533. }
  534. return false;
  535. }
  536. // Ensure the path of aTargetFullFileName exists, and create it if needed:
  537. wxString outputPath( aTargetFullFileName->GetPath() );
  538. if( !wxFileName::DirExists( outputPath ) )
  539. {
  540. if( wxMkdir( outputPath ) )
  541. {
  542. if( aReporter )
  543. {
  544. msg.Printf( _( "Output directory \"%s\" created.\n" ), outputPath );
  545. aReporter->Report( msg, RPT_SEVERITY_INFO );
  546. return true;
  547. }
  548. }
  549. else
  550. {
  551. if( aReporter )
  552. {
  553. msg.Printf( _( "Cannot create output directory \"%s\".\n" ), outputPath );
  554. aReporter->Report( msg, RPT_SEVERITY_ERROR );
  555. }
  556. return false;
  557. }
  558. }
  559. return true;
  560. }
  561. #ifdef __WXMAC__
  562. wxString GetOSXKicadUserDataDir()
  563. {
  564. // According to wxWidgets documentation for GetUserDataDir:
  565. // Mac: ~/Library/Application Support/appname
  566. wxFileName udir( wxStandardPaths::Get().GetUserDataDir(), wxEmptyString );
  567. // Since appname is different if started via launcher or standalone binary
  568. // map all to "kicad" here
  569. udir.RemoveLastDir();
  570. udir.AppendDir( "kicad" );
  571. return udir.GetPath();
  572. }
  573. wxString GetOSXKicadMachineDataDir()
  574. {
  575. return wxT( "/Library/Application Support/kicad" );
  576. }
  577. wxString GetOSXKicadDataDir()
  578. {
  579. // According to wxWidgets documentation for GetDataDir:
  580. // Mac: appname.app/Contents/SharedSupport bundle subdirectory
  581. wxFileName ddir( wxStandardPaths::Get().GetDataDir(), wxEmptyString );
  582. // This must be mapped to main bundle for everything but kicad.app
  583. const wxArrayString dirs = ddir.GetDirs();
  584. if( dirs[dirs.GetCount() - 3] != wxT( "kicad.app" ) )
  585. {
  586. // Bundle structure resp. current path is
  587. // kicad.app/Contents/Applications/<standalone>.app/Contents/SharedSupport
  588. // and will be mapped to
  589. // kicad.app/Contents/SharedSupprt
  590. ddir.RemoveLastDir();
  591. ddir.RemoveLastDir();
  592. ddir.RemoveLastDir();
  593. ddir.RemoveLastDir();
  594. ddir.AppendDir( wxT( "SharedSupport" ) );
  595. }
  596. return ddir.GetPath();
  597. }
  598. #endif
  599. // add this only if it is not in wxWidgets (for instance before 3.1.0)
  600. #ifdef USE_KICAD_WXSTRING_HASH
  601. size_t std::hash<wxString>::operator()( const wxString& s ) const
  602. {
  603. return std::hash<std::wstring>{}( s.ToStdWstring() );
  604. }
  605. #endif
  606. #ifdef USE_KICAD_WXPOINT_LESS_AND_HASH
  607. size_t std::hash<wxPoint>::operator() ( const wxPoint& k ) const
  608. {
  609. auto xhash = std::hash<int>()( k.x );
  610. // 0x9e3779b9 is 2^33 / ( 1 + sqrt(5) )
  611. // Adding this value ensures that consecutive bits of y will not be close to each other
  612. // decreasing the likelihood of hash collision in similar values of x and y
  613. return xhash ^ ( std::hash<int>()( k.y ) + 0x9e3779b9 + ( xhash << 6 ) + ( xhash >> 2 ) );
  614. }
  615. bool std::less<wxPoint>::operator()( const wxPoint& aA, const wxPoint& aB ) const
  616. {
  617. if( aA.x == aB.x )
  618. return aA.y < aB.y;
  619. return aA.x < aB.x;
  620. }
  621. #endif
  622. std::ostream& operator<<( std::ostream& out, const wxSize& size )
  623. {
  624. out << " width=\"" << size.GetWidth() << "\" height=\"" << size.GetHeight() << "\"";
  625. return out;
  626. }
  627. std::ostream& operator<<( std::ostream& out, const wxPoint& pt )
  628. {
  629. out << " x=\"" << pt.x << "\" y=\"" << pt.y << "\"";
  630. return out;
  631. }
  632. /**
  633. * Performance enhancements to file and directory operations.
  634. *
  635. * Note: while it's annoying to have to make copies of wxWidgets stuff and then
  636. * add platform-specific performance optimizations, the following routines offer
  637. * SIGNIFICANT performance benefits.
  638. */
  639. /**
  640. * WX_FILENAME
  641. *
  642. * A wrapper around a wxFileName which avoids expensive calls to wxFileName::SplitPath()
  643. * and string concatenations by caching the path and filename locally and only resolving
  644. * the wxFileName when it has to.
  645. */
  646. WX_FILENAME::WX_FILENAME( const wxString& aPath, const wxString& aFilename ) :
  647. m_fn( aPath, aFilename ),
  648. m_path( aPath ),
  649. m_fullName( aFilename )
  650. { }
  651. void WX_FILENAME::SetFullName( const wxString& aFileNameAndExtension )
  652. {
  653. m_fullName = aFileNameAndExtension;
  654. }
  655. wxString WX_FILENAME::GetName() const
  656. {
  657. size_t dot = m_fullName.find_last_of( wxT( '.' ) );
  658. return m_fullName.substr( 0, dot );
  659. }
  660. wxString WX_FILENAME::GetFullName() const
  661. {
  662. return m_fullName;
  663. }
  664. wxString WX_FILENAME::GetPath() const
  665. {
  666. return m_path;
  667. }
  668. wxString WX_FILENAME::GetFullPath() const
  669. {
  670. return m_path + wxT( '/' ) + m_fullName;
  671. }
  672. // Write locally-cached values to the wxFileName. MUST be called before using m_fn.
  673. void WX_FILENAME::resolve()
  674. {
  675. size_t dot = m_fullName.find_last_of( wxT( '.' ) );
  676. m_fn.SetName( m_fullName.substr( 0, dot ) );
  677. m_fn.SetExt( m_fullName.substr( dot + 1 ) );
  678. }
  679. long long WX_FILENAME::GetTimestamp()
  680. {
  681. resolve();
  682. if( m_fn.FileExists() )
  683. return m_fn.GetModificationTime().GetValue().GetValue();
  684. return 0;
  685. }
  686. /**
  687. * A copy of wxMatchWild(), which wxWidgets attributes to Douglas A. Lewis
  688. * <dalewis@cs.Buffalo.EDU> and ircII's reg.c.
  689. *
  690. * This version is modified to skip any encoding conversions (for performance).
  691. */
  692. bool matchWild( const char* pat, const char* text, bool dot_special )
  693. {
  694. if( !*text )
  695. {
  696. /* Match if both are empty. */
  697. return !*pat;
  698. }
  699. const char *m = pat,
  700. *n = text,
  701. *ma = NULL,
  702. *na = NULL;
  703. int just = 0,
  704. acount = 0,
  705. count = 0;
  706. if( dot_special && (*n == '.') )
  707. {
  708. /* Never match so that hidden Unix files
  709. * are never found. */
  710. return false;
  711. }
  712. for(;;)
  713. {
  714. if( *m == '*' )
  715. {
  716. ma = ++m;
  717. na = n;
  718. just = 1;
  719. acount = count;
  720. }
  721. else if( *m == '?' )
  722. {
  723. m++;
  724. if( !*n++ )
  725. return false;
  726. }
  727. else
  728. {
  729. if( *m == '\\' )
  730. {
  731. m++;
  732. /* Quoting "nothing" is a bad thing */
  733. if( !*m )
  734. return false;
  735. }
  736. if( !*m )
  737. {
  738. /*
  739. * If we are out of both strings or we just
  740. * saw a wildcard, then we can say we have a
  741. * match
  742. */
  743. if( !*n )
  744. return true;
  745. if( just )
  746. return true;
  747. just = 0;
  748. goto not_matched;
  749. }
  750. /*
  751. * We could check for *n == NULL at this point, but
  752. * since it's more common to have a character there,
  753. * check to see if they match first (m and n) and
  754. * then if they don't match, THEN we can check for
  755. * the NULL of n
  756. */
  757. just = 0;
  758. if( *m == *n )
  759. {
  760. m++;
  761. count++;
  762. n++;
  763. }
  764. else
  765. {
  766. not_matched:
  767. /*
  768. * If there are no more characters in the
  769. * string, but we still need to find another
  770. * character (*m != NULL), then it will be
  771. * impossible to match it
  772. */
  773. if( !*n )
  774. return false;
  775. if( ma )
  776. {
  777. m = ma;
  778. n = ++na;
  779. count = acount;
  780. }
  781. else
  782. return false;
  783. }
  784. }
  785. }
  786. }
  787. /**
  788. * A copy of ConvertFileTimeToWx() because wxWidgets left it as a static function
  789. * private to src/common/filename.cpp.
  790. */
  791. #if wxUSE_DATETIME && defined(__WIN32__) && !defined(__WXMICROWIN__)
  792. // Convert between wxDateTime and FILETIME which is a 64-bit value representing
  793. // the number of 100-nanosecond intervals since January 1, 1601 UTC.
  794. //
  795. // This is the offset between FILETIME epoch and the Unix/wxDateTime Epoch.
  796. static wxInt64 EPOCH_OFFSET_IN_MSEC = wxLL(11644473600000);
  797. static void ConvertFileTimeToWx( wxDateTime *dt, const FILETIME &ft )
  798. {
  799. wxLongLong t( ft.dwHighDateTime, ft.dwLowDateTime );
  800. t /= 10000; // Convert hundreds of nanoseconds to milliseconds.
  801. t -= EPOCH_OFFSET_IN_MSEC;
  802. *dt = wxDateTime( t );
  803. }
  804. #endif // wxUSE_DATETIME && __WIN32__
  805. /**
  806. * TimestampDir
  807. *
  808. * This routine offers SIGNIFICANT performance benefits over using wxWidgets to gather
  809. * timestamps from matching files in a directory.
  810. * @param aDirPath the directory to search
  811. * @param aFilespec a (wildcarded) file spec to match against
  812. * @return a hash of the last-mod-dates of all matching files in the directory
  813. */
  814. long long TimestampDir( const wxString& aDirPath, const wxString& aFilespec )
  815. {
  816. long long timestamp = 0;
  817. #if defined( __WIN32__ )
  818. // Win32 version.
  819. // Save time by not searching for each path twice: once in wxDir.GetNext() and once in
  820. // wxFileName.GetModificationTime(). Also cuts out wxWidgets' string-matching and case
  821. // conversion by staying on the MSW side of things.
  822. std::wstring filespec( aDirPath.t_str() );
  823. filespec += '\\';
  824. filespec += aFilespec.t_str();
  825. WIN32_FIND_DATA findData;
  826. wxDateTime lastModDate;
  827. HANDLE fileHandle = ::FindFirstFile( filespec.data(), &findData );
  828. if( fileHandle != INVALID_HANDLE_VALUE )
  829. {
  830. do
  831. {
  832. ConvertFileTimeToWx( &lastModDate, findData.ftLastWriteTime );
  833. timestamp += lastModDate.GetValue().GetValue();
  834. }
  835. while ( FindNextFile( fileHandle, &findData ) != 0 );
  836. }
  837. FindClose( fileHandle );
  838. #else
  839. // POSIX version.
  840. // Save time by not converting between encodings -- do everything on the file-system side.
  841. std::string filespec( aFilespec.fn_str() );
  842. std::string dir_path( aDirPath.fn_str() );
  843. DIR* dir = opendir( dir_path.c_str() );
  844. if( dir )
  845. {
  846. for( dirent* dir_entry = readdir( dir ); dir_entry; dir_entry = readdir( dir ) )
  847. {
  848. if( !matchWild( filespec.c_str(), dir_entry->d_name, true ) )
  849. continue;
  850. std::string entry_path = dir_path + '/' + dir_entry->d_name;
  851. struct stat entry_stat;
  852. if( wxCRT_Lstat( entry_path.c_str(), &entry_stat ) == 0 )
  853. {
  854. // Timestamp the source file, not the symlink
  855. if( S_ISLNK( entry_stat.st_mode ) ) // wxFILE_EXISTS_SYMLINK
  856. {
  857. char buffer[ PATH_MAX + 1 ];
  858. ssize_t pathLen = readlink( entry_path.c_str(), buffer, PATH_MAX );
  859. if( pathLen > 0 )
  860. {
  861. struct stat linked_stat;
  862. buffer[ pathLen ] = '\0';
  863. entry_path = dir_path + buffer;
  864. if( wxCRT_Lstat( entry_path.c_str(), &linked_stat ) == 0 )
  865. {
  866. entry_stat = linked_stat;
  867. }
  868. else
  869. {
  870. // if we couldn't lstat the linked file we'll have to just use
  871. // the symbolic link info
  872. }
  873. }
  874. }
  875. if( S_ISREG( entry_stat.st_mode ) ) // wxFileExists()
  876. timestamp += entry_stat.st_mtime * 1000;
  877. }
  878. else
  879. {
  880. // if we couldn't lstat the file itself all we can do is use the name
  881. timestamp += (signed) std::hash<std::string>{}( std::string( dir_entry->d_name ) );
  882. }
  883. }
  884. closedir( dir );
  885. }
  886. #endif
  887. return timestamp;
  888. }