KiCad PCB EDA Suite
panel_packages_view.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) 2021 Andrew Lutsenko, anlutsenko at gmail dot com
5  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "panel_packages_view.h"
22 #include "bitmaps.h"
23 #include "grid_tricks.h"
24 #include "kicad_settings.h"
25 #include "pgm_base.h"
27 
28 #include <cmath>
29 #include <fstream>
30 #include <wx/filedlg.h>
31 #include <wx/font.h>
32 #include <wx/tokenzr.h>
33 
34 
35 #define GRID_CELL_MARGIN 4
36 
37 
38 std::unordered_map<PCM_PACKAGE_VERSION_STATUS, wxString> PANEL_PACKAGES_VIEW::STATUS_ENUM_TO_STR = {
39  { PVS_INVALID, "invalid" },
40  { PVS_STABLE, "stable" },
41  { PVS_TESTING, "testing" },
42  { PVS_DEVELOPMENT, "development" },
43  { PVS_DEPRECATED, "deprecated" }
44 };
45 
46 
48  std::shared_ptr<PLUGIN_CONTENT_MANAGER> aPcm ) :
49  PANEL_PACKAGES_VIEW_BASE( parent ),
50  m_pcm( aPcm )
51 {
52  m_searchBitmap->SetBitmap( KiBitmap( BITMAPS::find, 24 ) );
53  m_gridVersions->PushEventHandler( new GRID_TRICKS( m_gridVersions ) );
54 
55  for( int col = 0; col < m_gridVersions->GetNumberCols(); col++ )
56  {
57  const wxString& heading = m_gridVersions->GetColLabelValue( col );
58  int headingWidth = GetTextExtent( heading ).x + 2 * GRID_CELL_MARGIN;
59 
60  // Set the minimal width to the column label size.
61  m_gridVersions->SetColMinimalWidth( col, headingWidth );
62  m_gridVersions->SetColSize( col,
63  m_gridVersions->GetVisibleWidth( col, true, true, false ) );
64  }
65 
66  m_infoText->SetBackgroundColour( wxStaticText::GetClassDefaultAttributes().colBg );
67 
68  // Try to disable the caret on platforms that show it even in read-only controls
69  m_infoText->Bind( wxEVT_SET_FOCUS,
70  [&]( wxFocusEvent& event )
71  {
72  wxCaret* caret = m_infoText->GetCaret();
73 
74  if( caret )
75  caret->Hide();
76  } );
77 
78  ClearData();
79 }
80 
81 
83 {
84  m_gridVersions->PopEventHandler( true );
85 }
86 
87 
89 {
91 
92  m_currentSelected = nullptr;
93  m_packagePanels.clear();
94  m_packageInitialOrder.clear();
95  m_packageListWindow->GetSizer()->Clear( true ); // Delete panels
96  m_packageListWindow->GetSizer()->FitInside( m_packageListWindow );
97  m_packageListWindow->Layout();
98 }
99 
100 
101 void PANEL_PACKAGES_VIEW::SetData( const std::vector<PACKAGE_VIEW_DATA>& aPackageData,
102  ActionCallback aCallback )
103 {
104  m_actionCallback = aCallback;
105 
106  ClearData();
107 
108  for( const PACKAGE_VIEW_DATA& data : aPackageData )
109  {
110  PANEL_PACKAGE* package_panel =
112 
113  package_panel->SetSelectCallback(
114  [package_panel, this]()
115  {
116  if( m_currentSelected && m_currentSelected != package_panel )
117  m_currentSelected->SetSelected( false );
118 
119  package_panel->SetSelected( true );
120  m_currentSelected = package_panel;
121  setPackageDetails( package_panel->GetPackageData() );
122 
123  Layout();
124  } );
125 
126  m_packagePanels.insert( { data.package.identifier, package_panel } );
127  m_packageInitialOrder.push_back( data.package.identifier );
128  }
129 
131 }
132 
133 
135 {
136  const PCM_PACKAGE& package = aPackageData.package;
137 
138  // Details
139  m_infoText->Clear();
140 
141  m_infoText->BeginParagraphSpacing( 0, 30 );
142  m_infoText->WriteText( package.description_full );
143  m_infoText->Newline();
144  m_infoText->EndParagraphSpacing();
145 
146  m_infoText->BeginFontSize( floor( m_infoText->GetDefaultStyle().GetFontSize() * 1.1 ) );
147  m_infoText->WriteText( _( "Metadata" ) );
148  m_infoText->Newline();
149  m_infoText->EndFontSize();
150 
151  m_infoText->BeginParagraphSpacing( 0, 10 );
152  m_infoText->BeginSymbolBullet( wxString::FromUTF8( u8"\u25CF" ), 30, 40 );
153  m_infoText->WriteText(
154  wxString::Format( _( "Package identifier: %s\n" ), package.identifier ) );
155  m_infoText->WriteText( wxString::Format( _( "License: %s\n" ), package.license ) );
156 
157  if( package.tags.size() > 0 )
158  {
159  wxString tags_str;
160 
161  for( const wxString& tag : package.tags )
162  {
163  if( !tags_str.IsEmpty() )
164  tags_str += ", ";
165 
166  tags_str += tag;
167  }
168 
169  m_infoText->WriteText( wxString::Format( _( "Tags: %s\n" ), tags_str ) );
170  }
171 
172  const auto write_contact = [&]( const wxString& type, const PCM_CONTACT& contact )
173  {
174  m_infoText->WriteText( wxString::Format( "%s: %s\n", type, contact.name ) );
175 
176  m_infoText->BeginLeftIndent( 60, 40 );
177 
178  for( const auto& entry : contact.contact )
179  m_infoText->WriteText( wxString::Format( "%s: %s\n", entry.first, entry.second ) );
180 
181  m_infoText->EndLeftIndent();
182  };
183 
184  write_contact( _( "Author" ), package.author );
185 
186  if( package.maintainer )
187  write_contact( _( "Maintainer" ), package.maintainer.get() );
188 
189  if( package.resources.size() > 0 )
190  {
191  m_infoText->WriteText( _( "Resources" ) );
192  m_infoText->Newline();
193 
194  m_infoText->BeginLeftIndent( 60, 40 );
195 
196  for( const auto& entry : package.resources )
197  {
198  m_infoText->WriteText( wxString::Format( "%s: %s\n", entry.first, entry.second ) );
199  }
200 
201  m_infoText->EndLeftIndent();
202  }
203 
204  m_infoText->EndSymbolBullet();
205  m_infoText->EndParagraphSpacing();
206 
207  // Versions table
208  m_gridVersions->Freeze();
209 
210  if( m_gridVersions->GetNumberRows() != 0 )
211  m_gridVersions->DeleteRows( 0, m_gridVersions->GetNumberRows() );
212 
213  int row = 0;
214  wxString current_version;
215 
216  if( aPackageData.state == PPS_INSTALLED )
217  current_version = m_pcm->GetInstalledPackageVersion( package.identifier );
218 
219  wxFont bold_font = m_gridVersions->GetDefaultCellFont().Bold();
220 
221  for( const PACKAGE_VERSION& version : package.versions )
222  {
223  if( !version.compatible && !m_showAllVersions->IsChecked() )
224  continue;
225 
226  m_gridVersions->InsertRows( row );
227 
228  m_gridVersions->SetCellValue( row, COL_VERSION, version.version );
229  m_gridVersions->SetCellValue( row, COL_DOWNLOAD_SIZE,
230  toHumanReadableSize( version.download_size ) );
231  m_gridVersions->SetCellValue( row, COL_INSTALL_SIZE,
232  toHumanReadableSize( version.install_size ) );
233  m_gridVersions->SetCellValue( row, COL_COMPATIBILITY,
234  version.compatible ? wxT( "\u2714" ) : wxEmptyString );
235  m_gridVersions->SetCellValue( row, COL_STATUS, STATUS_ENUM_TO_STR.at( version.status ) );
236 
237  m_gridVersions->SetCellAlignment( row, COL_COMPATIBILITY, wxALIGN_CENTER, wxALIGN_CENTER );
238 
239  if( current_version == version.version )
240  {
241  for( int col = 0; col < m_gridVersions->GetNumberCols(); col++ )
242  m_gridVersions->SetCellFont( row, col, bold_font );
243  }
244 
245  row++;
246  }
247 
248  for( int col = 0; col < m_gridVersions->GetNumberCols(); col++ )
249  {
250  // Set the width to see the full contents
251  m_gridVersions->SetColSize( col,
252  m_gridVersions->GetVisibleWidth( col, true, true, false ) );
253  }
254 
255  m_gridVersions->Thaw();
256 
257  if( aPackageData.state == PPS_AVAILABLE || aPackageData.state == PPS_UNAVAILABLE )
258  m_buttonInstall->Enable();
259  else
260  m_buttonInstall->Disable();
261 }
262 
263 
265 {
266  m_infoText->ChangeValue( _( "Pick a package on the left panel to view it's description." ) );
267 
268  m_gridVersions->Freeze();
269 
270  if( m_gridVersions->GetNumberRows() > 0 )
271  m_gridVersions->DeleteRows( 0, m_gridVersions->GetNumberRows() );
272 
273  m_gridVersions->Thaw();
274 }
275 
276 
277 wxString PANEL_PACKAGES_VIEW::toHumanReadableSize( const boost::optional<uint64_t> size ) const
278 {
279  if( !size )
280  return "-";
281 
282  uint64_t b = size.get();
283 
284  if( b >= 1024 * 1024 )
285  return wxString::Format( "%.1f Mb", b / 1024.0 / 1024.0 );
286 
287  if( b >= 1024 )
288  return wxString::Format( "%lld Kb", b / 1024 );
289 
290  return wxString::Format( "%lld b", b );
291 }
292 
293 
294 void PANEL_PACKAGES_VIEW::SetPackageState( const wxString& aPackageId,
295  const PCM_PACKAGE_STATE aState ) const
296 {
297  auto it = m_packagePanels.find( aPackageId );
298 
299  if( it != m_packagePanels.end() )
300  {
301  it->second->SetState( aState );
302 
303  if( m_currentSelected && m_currentSelected == it->second )
304  {
305  wxMouseEvent dummy;
307  }
308  }
309 }
310 
311 
313 {
314  m_gridVersions->ClearSelection();
315  m_gridVersions->SelectRow( event.GetRow() );
316 }
317 
318 
320 {
321  const auto rows = m_gridVersions->GetSelectedRows();
322 
323  if( !m_currentSelected || rows.size() != 1 )
324  {
325  wxBell();
326  return;
327  }
328 
329  wxString version = m_gridVersions->GetCellValue( rows[0], COL_VERSION );
330  const PCM_PACKAGE& package = m_currentSelected->GetPackageData().package;
331 
332  auto ver_it = std::find_if( package.versions.begin(), package.versions.end(),
333  [&]( const PACKAGE_VERSION& ver )
334  {
335  return ver.version == version;
336  } );
337 
338  wxASSERT_MSG( ver_it != package.versions.end(), "Could not find package version" );
339 
340  if( !ver_it->download_url )
341  {
342  wxMessageBox( _( "Package download url is not specified" ),
343  _( "Error downloading package" ), wxICON_INFORMATION | wxOK, this );
344  return;
345  }
346 
347  const wxString& url = ver_it->download_url.get();
348 
349  SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
350  KICAD_SETTINGS* app_settings = mgr.GetAppSettings<KICAD_SETTINGS>();
351 
352  wxFileDialog dialog( this, _( "Save package" ), app_settings->m_PcmLastDownloadDir,
353  wxString::Format( "%s_v%s.zip", package.identifier, version ),
354  "ZIP files (*.zip)|*.zip", wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
355 
356  if( dialog.ShowModal() == wxID_CANCEL )
357  return;
358 
359  wxString path = dialog.GetPath();
360  app_settings->m_PcmLastDownloadDir = wxPathOnly( path );
361 
362  std::ofstream output( path.ToUTF8(), std::ios_base::binary );
363 
364  bool success = m_pcm->DownloadToStream( url, &output, _( "Downloading package" ), 0 );
365 
366  output.close();
367 
368  if( success )
369  {
370  if( ver_it->download_sha256 )
371  {
372  std::ifstream stream( path.ToUTF8(), std::ios_base::binary );
373 
374  bool matches = m_pcm->VerifyHash( stream, ver_it->download_sha256.get() );
375 
376  stream.close();
377 
378  if( !matches
379  && wxMessageBox(
380  _( "Integrity of the downloaded package could not be verified, hash "
381  "does not match. Are you sure you want to keep this file?" ),
382  _( "Keep downloaded file" ), wxICON_EXCLAMATION | wxYES_NO, this )
383  == wxNO )
384  {
385  wxRemoveFile( path );
386  }
387  }
388  }
389  else
390  {
391  if( wxFileExists( path ) )
392  wxRemoveFile( path );
393  }
394 }
395 
396 
397 void PANEL_PACKAGES_VIEW::OnInstallVersionClicked( wxCommandEvent& event )
398 {
399  const auto rows = m_gridVersions->GetSelectedRows();
400 
401  if( m_currentSelected && rows.size() != 1 )
402  {
403  wxBell();
404  return;
405  }
406 
407  wxString version = m_gridVersions->GetCellValue( rows[0], COL_VERSION );
408  const PCM_PACKAGE& package = m_currentSelected->GetPackageData().package;
409 
410  auto ver_it = std::find_if( package.versions.begin(), package.versions.end(),
411  [&]( const PACKAGE_VERSION& ver )
412  {
413  return ver.version == version;
414  } );
415 
416  wxASSERT_MSG( ver_it != package.versions.end(), "Could not find package version" );
417 
418  if( !ver_it->compatible
419  && wxMessageBox( _( "This package version is incompatible with your kicad version or "
420  "platform. Are you sure you want to install it anyway?" ),
421  _( "Install package" ), wxICON_EXCLAMATION | wxYES_NO, this )
422  == wxNO )
423  {
424  return;
425  }
426 
428 }
429 
430 
432 {
433  if( m_currentSelected )
434  {
435  wxMouseEvent dummy;
437  }
438 }
439 
440 
441 void PANEL_PACKAGES_VIEW::OnSearchTextChanged( wxCommandEvent& event )
442 {
444 
445  if( m_currentSelected )
446  m_currentSelected->SetSelected( false );
447 
448  m_currentSelected = nullptr;
449 
451 }
452 
453 
455 {
456  // Sort by descending rank, ascending index
457  std::vector<std::pair<int, int>> package_ranks;
458 
459  const wxString search_term = m_searchCtrl->GetValue().Trim();
460 
461  for( size_t index = 0; index < m_packageInitialOrder.size(); index++ )
462  {
463  int rank = 1;
464  const PCM_PACKAGE& pkg =
465  m_packagePanels[m_packageInitialOrder[index]]->GetPackageData().package;
466 
467  if( search_term.size() > 2 )
468  rank = m_pcm->GetPackageSearchRank( pkg, search_term );
469 
470  // Packages with no versions are delisted and should not be shown
471  if( pkg.versions.size() == 0 )
472  rank = 0;
473 
474  package_ranks.emplace_back( rank, index );
475  }
476 
477  std::sort( package_ranks.begin(), package_ranks.end(),
478  []( const std::pair<int, int>& a, const std::pair<int, int>& b )
479  {
480  return a.first > b.first || ( a.first == b.first && a.second < b.second );
481  } );
482 
483  // Rearrange panels, hide ones with 0 rank
484  wxSizer* sizer = m_packageListWindow->GetSizer();
485  sizer->Clear( false ); // Don't delete panels
486 
487  for( const auto& pair : package_ranks )
488  {
489  PANEL_PACKAGE* panel = m_packagePanels[m_packageInitialOrder[pair.second]];
490 
491  if( pair.first > 0 )
492  {
493  sizer->Add( panel, 0, wxEXPAND );
494  panel->Show();
495  }
496  else
497  {
498  panel->Hide();
499  }
500  }
501 
502  sizer->FitInside( m_packageListWindow );
503  m_packageListWindow->SetScrollRate( 0, 15 );
504  m_packageListWindow->Layout();
505 }
< Package version metadata Package metadata
Definition: pcm_data.h:73
void SetData(const std::vector< PACKAGE_VIEW_DATA > &aPackageData, ActionCallback aCallback)
Recreates package panels and displays daya.
std::shared_ptr< PLUGIN_CONTENT_MANAGER > m_pcm
PCM_PACKAGE_VERSION_STATUS status
Definition: pcm_data.h:81
wxString toHumanReadableSize(const boost::optional< uint64_t > size) const
boost::optional< uint64_t > download_size
Definition: pcm_data.h:79
boost::optional< uint64_t > install_size
Definition: pcm_data.h:80
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition: grid_tricks.h:55
std::vector< wxString > m_packageInitialOrder
void ClearData()
Selects full row of the clicked cell.
void unsetPackageDetails()
Bytes to Kb/Mb/Gb string or "-" if absent.
void OnInstallVersionClicked(wxCommandEvent &event) override
Shows all versions including incompatible ones.
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
int GetVisibleWidth(int aCol, bool aHeader=true, bool aContents=false, bool aKeep=true)
Calculates the specified column based on the actual size of the text on screen.
Definition: wx_grid.cpp:246
< Collection of data relevant to the package display panel Callback for (un)install button
Definition: panel_package.h:30
static LIB_SYMBOL * dummy()
Used to draw a dummy shape when a LIB_SYMBOL is not found in library.
Definition: sch_symbol.cpp:72
static std::unordered_map< PCM_PACKAGE_VERSION_STATUS, wxString > STATUS_ENUM_TO_STR
void SetPackageState(const wxString &aPackageId, const PCM_PACKAGE_STATE aState) const
Set the state of package.
bool compatible
Definition: pcm_data.h:88
void OnShowAllVersionsClicked(wxCommandEvent &event) override
Ranks packages for entered search term and rearranges/hides panels according to their rank.
void SetSelected(bool aSelected)
Class PANEL_PACKAGES_VIEW_BASE.
Repository reference to a resource.
Definition: pcm_data.h:93
void OnDownloadVersionClicked(wxCommandEvent &event) override
Schedules installation of selected package version.
T * GetAppSettings(bool aLoadNow=true)
Returns a handle to the a given settings by type If the settings have already been loaded,...
#define _(s)
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
wxScrolledWindow * m_packageListWindow
void OnClick(wxMouseEvent &event) override
void SetSelectCallback(const std::function< void()> &aCallback)
Marks panel as selected.
std::vector< PACKAGE_VERSION > versions
Definition: pcm_data.h:105
void OnVersionsCellClicked(wxGridEvent &event) override
Opens file chooser dialog and downloads selected package version archive.
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 OnSearchTextChanged(wxCommandEvent &event) override
PCM_PACKAGE_STATE
Definition: pcm.h:51
PCM_PACKAGE_STATE state
Definition: panel_package.h:34
void setPackageDetails(const PACKAGE_VIEW_DATA &aPackageData)
Clears details panel.
see class PGM_BASE
const PACKAGE_VIEW_DATA & GetPackageData() const
Definition: panel_package.h:77
PANEL_PACKAGE * m_currentSelected
void updatePackageList()
< Updates package listing according to search term
std::unordered_map< wxString, PANEL_PACKAGE * > m_packagePanels
std::function< void(const PACKAGE_VIEW_DATA &aData, PCM_PACKAGE_ACTION aAction, const wxString &aVersion)> ActionCallback
Definition: panel_package.h:52
PANEL_PACKAGES_VIEW(wxWindow *parent, std::shared_ptr< PLUGIN_CONTENT_MANAGER > aPcm)
ActionCallback m_actionCallback
wxString version
Definition: pcm_data.h:75
#define GRID_CELL_MARGIN