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.

373 lines
9.4 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2016-2017 KiCad Developers, see AUTHORS.txt for contributors.
  5. * Copyright (C) 2017 Chris Pavlina <pavlina.chris@gmail.com>
  6. * Copyright (C) 2016 Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
  7. *
  8. * This program is free software: you can redistribute it and/or modify it
  9. * under the terms of the GNU General Public License as published by the
  10. * Free Software Foundation, either version 3 of the License, or (at your
  11. * option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful, but
  14. * WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License along
  19. * with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. #include <footprint_preview_panel.h>
  22. #include <pcb_draw_panel_gal.h>
  23. #include <kiway.h>
  24. #include <io_mgr.h>
  25. #include <fp_lib_table.h>
  26. #include <view/view.h>
  27. #include <math/box2.h>
  28. #include <class_module.h>
  29. #include <class_board.h>
  30. #include <ki_mutex.h>
  31. #include <boost/bind.hpp>
  32. #include <utility>
  33. #include <make_unique.h>
  34. #include <colors_design_settings.h>
  35. #include <wx/stattext.h>
  36. /**
  37. * Threadsafe interface class between loader thread and panel class.
  38. */
  39. class FP_THREAD_IFACE
  40. {
  41. using CACHE_ENTRY = FOOTPRINT_PREVIEW_PANEL::CACHE_ENTRY;
  42. public:
  43. /// Retrieve a cache entry by LIB_ID
  44. OPT<CACHE_ENTRY> GetFromCache( LIB_ID const & aFPID )
  45. {
  46. MUTLOCK lock( m_lock );
  47. auto it = m_cachedFootprints.find( aFPID );
  48. if( it != m_cachedFootprints.end() )
  49. return it->second;
  50. else
  51. return NULLOPT;
  52. }
  53. /**
  54. * Push an entry to the loading queue and a placeholder to the cache;
  55. * return the placeholder.
  56. */
  57. CACHE_ENTRY AddToQueue( LIB_ID const & aEntry )
  58. {
  59. MUTLOCK lock( m_lock );
  60. CACHE_ENTRY ent = { aEntry, NULL, FPS_LOADING };
  61. m_cachedFootprints[aEntry] = ent;
  62. m_loaderQueue.push_back( ent );
  63. return ent;
  64. }
  65. /// Pop an entry from the queue, or empty option if none is available.
  66. OPT<CACHE_ENTRY> PopFromQueue()
  67. {
  68. MUTLOCK lock( m_lock );
  69. if( m_loaderQueue.empty() )
  70. {
  71. return NULLOPT;
  72. }
  73. else
  74. {
  75. auto ent = m_loaderQueue.front();
  76. m_loaderQueue.pop_front();
  77. return ent;
  78. }
  79. }
  80. /// Add an entry to the cache.
  81. void AddToCache( CACHE_ENTRY const & aEntry )
  82. {
  83. MUTLOCK lock( m_lock );
  84. m_cachedFootprints[aEntry.fpid] = aEntry;
  85. }
  86. /**
  87. * Threadsafe accessor to set the current footprint.
  88. */
  89. void SetCurrentFootprint( LIB_ID aFp )
  90. {
  91. MUTLOCK lock( m_lock );
  92. m_current_fp = std::move( aFp );
  93. }
  94. /**
  95. * Threadsafe accessor to get the current footprint.
  96. */
  97. LIB_ID GetCurrentFootprint()
  98. {
  99. MUTLOCK lock( m_lock );
  100. return m_current_fp;
  101. }
  102. /**
  103. * Set the associated panel, for QueueEvent() and GetTable().
  104. */
  105. void SetPanel( FOOTPRINT_PREVIEW_PANEL* aPanel )
  106. {
  107. MUTLOCK lock( m_lock );
  108. m_panel = aPanel;
  109. }
  110. /**
  111. * Get the associated panel.
  112. */
  113. FOOTPRINT_PREVIEW_PANEL* GetPanel()
  114. {
  115. MUTLOCK lock( m_lock );
  116. return m_panel;
  117. }
  118. /**
  119. * Post an event to the panel, if the panel still exists. Return whether
  120. * the event was posted.
  121. */
  122. bool QueueEvent( wxEvent const& aEvent )
  123. {
  124. MUTLOCK lock( m_lock );
  125. if( m_panel )
  126. {
  127. m_panel->GetEventHandler()->QueueEvent( aEvent.Clone() );
  128. return true;
  129. }
  130. else
  131. {
  132. return false;
  133. }
  134. }
  135. /**
  136. * Get an FP_LIB_TABLE, or null if the panel is dead.
  137. */
  138. FP_LIB_TABLE* GetTable()
  139. {
  140. MUTLOCK locK( m_lock );
  141. return m_panel ? m_panel->Prj().PcbFootprintLibs() : nullptr;
  142. }
  143. private:
  144. std::deque<CACHE_ENTRY> m_loaderQueue;
  145. std::map<LIB_ID, CACHE_ENTRY> m_cachedFootprints;
  146. LIB_ID m_current_fp;
  147. FOOTPRINT_PREVIEW_PANEL* m_panel = nullptr;
  148. MUTEX m_lock;
  149. };
  150. /**
  151. * Footprint loader thread to prevent footprint loading from locking the UI.
  152. * Interface is via a FP_THREAD_IFACE.
  153. */
  154. class FP_LOADER_THREAD: public wxThread
  155. {
  156. using CACHE_ENTRY = FOOTPRINT_PREVIEW_PANEL::CACHE_ENTRY;
  157. std::shared_ptr<FP_THREAD_IFACE> m_iface;
  158. public:
  159. FP_LOADER_THREAD( std::shared_ptr<FP_THREAD_IFACE> const& aIface ):
  160. wxThread( wxTHREAD_DETACHED ),
  161. m_iface( aIface )
  162. {}
  163. ~FP_LOADER_THREAD()
  164. {}
  165. void ProcessEntry( CACHE_ENTRY& aEntry )
  166. {
  167. FP_LIB_TABLE* fptbl = m_iface->GetTable();
  168. if( !fptbl )
  169. return;
  170. aEntry.module = NULL;
  171. try {
  172. aEntry.module = fptbl->FootprintLoadWithOptionalNickname( aEntry.fpid );
  173. if( !aEntry.module )
  174. aEntry.status = FPS_NOT_FOUND;
  175. } catch( const IO_ERROR& )
  176. {
  177. aEntry.status = FPS_NOT_FOUND;
  178. }
  179. if( aEntry.status != FPS_NOT_FOUND )
  180. aEntry.status = FPS_READY;
  181. m_iface->AddToCache( aEntry );
  182. if( aEntry.fpid == m_iface->GetCurrentFootprint() )
  183. {
  184. wxCommandEvent evt( wxEVT_COMMAND_TEXT_UPDATED, 1 );
  185. m_iface->QueueEvent( evt );
  186. }
  187. }
  188. virtual void* Entry() override
  189. {
  190. while( m_iface->GetPanel() )
  191. {
  192. auto ent = m_iface->PopFromQueue();
  193. if( ent )
  194. ProcessEntry( *ent );
  195. else
  196. wxMilliSleep( 100 );
  197. }
  198. return nullptr;
  199. }
  200. };
  201. FOOTPRINT_PREVIEW_PANEL::FOOTPRINT_PREVIEW_PANEL(
  202. KIWAY* aKiway, wxWindow* aParent, KIGFX::GAL_DISPLAY_OPTIONS& aOpts, GAL_TYPE aGalType )
  203. : PCB_DRAW_PANEL_GAL ( aParent, -1, wxPoint( 0, 0 ), wxSize(200, 200), aOpts, aGalType ),
  204. KIWAY_HOLDER( aKiway ),
  205. m_footprintDisplayed( true )
  206. {
  207. m_iface = std::make_shared<FP_THREAD_IFACE>();
  208. m_iface->SetPanel( this );
  209. m_loader = new FP_LOADER_THREAD( m_iface );
  210. m_loader->Run();
  211. SetStealsFocus( false );
  212. ShowScrollbars( wxSHOW_SB_NEVER, wxSHOW_SB_NEVER );
  213. EnableScrolling( false, false ); // otherwise Zoom Auto disables GAL canvas
  214. m_dummyBoard = std::make_unique<BOARD>();
  215. m_colorsSettings = std::make_unique<COLORS_DESIGN_SETTINGS>( FRAME_PCB_FOOTPRINT_PREVIEW );
  216. UseColorScheme( m_colorsSettings.get() );
  217. SyncLayersVisibility( &*m_dummyBoard );
  218. Raise();
  219. Show(true);
  220. StartDrawing();
  221. Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( FOOTPRINT_PREVIEW_PANEL::OnLoaderThreadUpdate ), NULL, this );
  222. }
  223. FOOTPRINT_PREVIEW_PANEL::~FOOTPRINT_PREVIEW_PANEL( )
  224. {
  225. m_iface->SetPanel( nullptr );
  226. }
  227. FOOTPRINT_PREVIEW_PANEL::CACHE_ENTRY FOOTPRINT_PREVIEW_PANEL::CacheAndReturn( const LIB_ID& aFPID )
  228. {
  229. auto opt_ent = m_iface->GetFromCache( aFPID );
  230. if( opt_ent )
  231. return *opt_ent;
  232. else
  233. return m_iface->AddToQueue( aFPID );
  234. }
  235. // This is separate to avoid having to export CACHE_ENTRY to the global namespace
  236. void FOOTPRINT_PREVIEW_PANEL::CacheFootprint( LIB_ID const& aFPID )
  237. {
  238. (void) CacheAndReturn( aFPID );
  239. }
  240. void FOOTPRINT_PREVIEW_PANEL::renderFootprint( MODULE *module )
  241. {
  242. GetView()->Clear();
  243. module->SetParent ( &*m_dummyBoard );
  244. GetView()->Add ( module );
  245. GetView()->SetVisible( module, true );
  246. GetView()->Update( module, KIGFX::ALL );
  247. BOX2I bbox = module->ViewBBox();
  248. bbox.Merge ( module->Value().ViewBBox() );
  249. bbox.Merge ( module->Reference().ViewBBox() );
  250. if( bbox.GetSize().x > 0 && bbox.GetSize().y > 0 )
  251. {
  252. // Autozoom
  253. GetView()->SetViewport( BOX2D( bbox.GetOrigin(), bbox.GetSize() ) );
  254. // Add a margin
  255. GetView()->SetScale( GetView()->GetScale() * 0.7 );
  256. Refresh();
  257. }
  258. }
  259. void FOOTPRINT_PREVIEW_PANEL::DisplayFootprint ( const LIB_ID& aFPID )
  260. {
  261. m_currentFPID = aFPID;
  262. m_iface->SetCurrentFootprint( aFPID );
  263. m_footprintDisplayed = false;
  264. CACHE_ENTRY fpe = CacheAndReturn ( m_currentFPID );
  265. if( m_handler )
  266. m_handler( fpe.status );
  267. if( fpe.status == FPS_READY )
  268. {
  269. if ( !m_footprintDisplayed )
  270. {
  271. renderFootprint( fpe.module );
  272. m_footprintDisplayed = true;
  273. Refresh();
  274. }
  275. }
  276. }
  277. void FOOTPRINT_PREVIEW_PANEL::OnLoaderThreadUpdate( wxCommandEvent& event )
  278. {
  279. DisplayFootprint( m_currentFPID );
  280. }
  281. void FOOTPRINT_PREVIEW_PANEL::SetStatusHandler( FOOTPRINT_STATUS_HANDLER aHandler )
  282. {
  283. m_handler = aHandler;
  284. }
  285. wxWindow* FOOTPRINT_PREVIEW_PANEL::GetWindow()
  286. {
  287. return static_cast<wxWindow*>( this );
  288. }
  289. FOOTPRINT_PREVIEW_PANEL* FOOTPRINT_PREVIEW_PANEL::New( KIWAY* aKiway, wxWindow* aParent )
  290. {
  291. KIGFX::GAL_DISPLAY_OPTIONS gal_opts;
  292. return new FOOTPRINT_PREVIEW_PANEL(
  293. aKiway, aParent, gal_opts, EDA_DRAW_PANEL_GAL::GAL_TYPE_CAIRO );
  294. }