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.

374 lines
9.5 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 <make_unique.h>
  33. #include <class_colors_design_settings.h>
  34. #include <wx/stattext.h>
  35. /**
  36. * Threadsafe interface class between loader thread and panel class.
  37. */
  38. class FP_THREAD_IFACE
  39. {
  40. using CACHE_ENTRY = FOOTPRINT_PREVIEW_PANEL::CACHE_ENTRY;
  41. public:
  42. /// Retrieve a cache entry by LIB_ID
  43. boost::optional<CACHE_ENTRY> GetFromCache( LIB_ID const & aFPID )
  44. {
  45. MUTLOCK lock( m_lock );
  46. auto it = m_cachedFootprints.find( aFPID );
  47. if( it != m_cachedFootprints.end() )
  48. return it->second;
  49. else
  50. return boost::none;
  51. }
  52. /**
  53. * Push an entry to the loading queue and a placeholder to the cache;
  54. * return the placeholder.
  55. */
  56. CACHE_ENTRY AddToQueue( LIB_ID const & aEntry )
  57. {
  58. MUTLOCK lock( m_lock );
  59. CACHE_ENTRY ent = { aEntry, NULL, FPS_LOADING };
  60. m_cachedFootprints[aEntry] = ent;
  61. m_loaderQueue.push_back( ent );
  62. return ent;
  63. }
  64. /// Pop an entry from the queue, or empty option if none is available.
  65. boost::optional<CACHE_ENTRY> PopFromQueue()
  66. {
  67. MUTLOCK lock( m_lock );
  68. if( m_loaderQueue.empty() )
  69. {
  70. return boost::none;
  71. }
  72. else
  73. {
  74. auto ent = m_loaderQueue.front();
  75. m_loaderQueue.pop_front();
  76. return ent;
  77. }
  78. }
  79. /// Add an entry to the cache.
  80. void AddToCache( CACHE_ENTRY const & aEntry )
  81. {
  82. MUTLOCK lock( m_lock );
  83. m_cachedFootprints[aEntry.fpid] = aEntry;
  84. }
  85. /**
  86. * Threadsafe accessor to set the current footprint.
  87. */
  88. void SetCurrentFootprint( LIB_ID aFp )
  89. {
  90. MUTLOCK lock( m_lock );
  91. m_current_fp = aFp;
  92. }
  93. /**
  94. * Threadsafe accessor to get the current footprint.
  95. */
  96. LIB_ID GetCurrentFootprint()
  97. {
  98. MUTLOCK lock( m_lock );
  99. return m_current_fp;
  100. }
  101. /**
  102. * Set the associated panel, for QueueEvent() and GetTable().
  103. */
  104. void SetPanel( FOOTPRINT_PREVIEW_PANEL* aPanel )
  105. {
  106. MUTLOCK lock( m_lock );
  107. m_panel = aPanel;
  108. }
  109. /**
  110. * Get the associated panel.
  111. */
  112. FOOTPRINT_PREVIEW_PANEL* GetPanel()
  113. {
  114. MUTLOCK lock( m_lock );
  115. return m_panel;
  116. }
  117. /**
  118. * Post an event to the panel, if the panel still exists. Return whether
  119. * the event was posted.
  120. */
  121. bool QueueEvent( wxEvent const& aEvent )
  122. {
  123. MUTLOCK lock( m_lock );
  124. if( m_panel )
  125. {
  126. m_panel->GetEventHandler()->QueueEvent( aEvent.Clone() );
  127. return true;
  128. }
  129. else
  130. {
  131. return false;
  132. }
  133. }
  134. /**
  135. * Get an FP_LIB_TABLE, or null if the panel is dead.
  136. */
  137. FP_LIB_TABLE* GetTable()
  138. {
  139. MUTLOCK locK( m_lock );
  140. return m_panel ? m_panel->Prj().PcbFootprintLibs() : nullptr;
  141. }
  142. private:
  143. std::deque<CACHE_ENTRY> m_loaderQueue;
  144. std::map<LIB_ID, CACHE_ENTRY> m_cachedFootprints;
  145. LIB_ID m_current_fp;
  146. FOOTPRINT_PREVIEW_PANEL* m_panel;
  147. MUTEX m_lock;
  148. };
  149. /**
  150. * Footprint loader thread to prevent footprint loading from locking the UI.
  151. * Interface is via a FP_THREAD_IFACE.
  152. */
  153. class FP_LOADER_THREAD: public wxThread
  154. {
  155. using CACHE_ENTRY = FOOTPRINT_PREVIEW_PANEL::CACHE_ENTRY;
  156. std::shared_ptr<FP_THREAD_IFACE> m_iface;
  157. public:
  158. FP_LOADER_THREAD( std::shared_ptr<FP_THREAD_IFACE> const& aIface ):
  159. wxThread( wxTHREAD_DETACHED ),
  160. m_iface( aIface )
  161. {}
  162. ~FP_LOADER_THREAD()
  163. {}
  164. void ProcessEntry( CACHE_ENTRY& aEntry )
  165. {
  166. FP_LIB_TABLE* fptbl = m_iface->GetTable();
  167. if( !fptbl )
  168. return;
  169. aEntry.module = NULL;
  170. try {
  171. aEntry.module = fptbl->FootprintLoadWithOptionalNickname( aEntry.fpid );
  172. if( !aEntry.module )
  173. aEntry.status = FPS_NOT_FOUND;
  174. } catch( const IO_ERROR& )
  175. {
  176. aEntry.status = FPS_NOT_FOUND;
  177. }
  178. if( aEntry.status != FPS_NOT_FOUND )
  179. aEntry.status = FPS_READY;
  180. m_iface->AddToCache( aEntry );
  181. if( aEntry.fpid == m_iface->GetCurrentFootprint() )
  182. {
  183. wxCommandEvent evt( wxEVT_COMMAND_TEXT_UPDATED, 1 );
  184. m_iface->QueueEvent( evt );
  185. }
  186. }
  187. virtual void* Entry() override
  188. {
  189. while( m_iface->GetPanel() )
  190. {
  191. auto ent = m_iface->PopFromQueue();
  192. if( ent )
  193. ProcessEntry( *ent );
  194. else
  195. wxMilliSleep( 100 );
  196. }
  197. return nullptr;
  198. }
  199. };
  200. FOOTPRINT_PREVIEW_PANEL::FOOTPRINT_PREVIEW_PANEL(
  201. KIWAY* aKiway, wxWindow* aParent, KIGFX::GAL_DISPLAY_OPTIONS& aOpts, GAL_TYPE aGalType )
  202. : PCB_DRAW_PANEL_GAL ( aParent, -1, wxPoint( 0, 0 ), wxSize(200, 200), aOpts, aGalType ),
  203. KIWAY_HOLDER( aKiway ),
  204. m_footprintDisplayed( true )
  205. {
  206. m_iface = std::make_shared<FP_THREAD_IFACE>();
  207. m_iface->SetPanel( this );
  208. m_loader = new FP_LOADER_THREAD( m_iface );
  209. m_loader->Run();
  210. SetStealsFocus( false );
  211. ShowScrollbars( wxSHOW_SB_NEVER, wxSHOW_SB_NEVER );
  212. EnableScrolling( false, false ); // otherwise Zoom Auto disables GAL canvas
  213. m_dummyBoard = std::make_unique<BOARD>();
  214. m_colorsSettings = std::make_unique<COLORS_DESIGN_SETTINGS>( FRAME_PCB_FOOTPRINT_PREVIEW );
  215. UseColorScheme( m_colorsSettings.get() );
  216. SyncLayersVisibility( &*m_dummyBoard );
  217. Raise();
  218. Show(true);
  219. StartDrawing();
  220. Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( FOOTPRINT_PREVIEW_PANEL::OnLoaderThreadUpdate ), NULL, this );
  221. }
  222. FOOTPRINT_PREVIEW_PANEL::~FOOTPRINT_PREVIEW_PANEL( )
  223. {
  224. m_iface->SetPanel( nullptr );
  225. }
  226. FOOTPRINT_PREVIEW_PANEL::CACHE_ENTRY FOOTPRINT_PREVIEW_PANEL::CacheAndReturn( const LIB_ID& aFPID )
  227. {
  228. auto opt_ent = m_iface->GetFromCache( aFPID );
  229. if( opt_ent )
  230. return *opt_ent;
  231. else
  232. return m_iface->AddToQueue( aFPID );
  233. }
  234. // This is separate to avoid having to export CACHE_ENTRY to the global namespace
  235. void FOOTPRINT_PREVIEW_PANEL::CacheFootprint( LIB_ID const& aFPID )
  236. {
  237. (void) CacheAndReturn( aFPID );
  238. }
  239. void FOOTPRINT_PREVIEW_PANEL::renderFootprint( MODULE *module )
  240. {
  241. GetView()->Clear();
  242. module->SetParent ( &*m_dummyBoard );
  243. module->RunOnChildren( boost::bind( &KIGFX::VIEW::Add, GetView(), _1, -1 ) );
  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. }