KiCad PCB EDA Suite
dialog_pcm.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 // kicad_curl_easy.h **must be** included before any wxWidgets header to avoid conflicts
22 // at least on Windows/msys2
24 
25 #include <dialog_pcm.h>
26 #include <bitmaps.h>
28 #include <grid_tricks.h>
29 #include <ki_exception.h>
30 #include <kicad_settings.h>
31 #include <pcm_task_manager.h>
32 #include <pgm_base.h>
34 #include <widgets/wx_grid.h>
36 
37 #include <fstream>
38 #include <sstream>
39 #include <thread>
40 #include <vector>
41 #include <wx/filedlg.h>
42 #include <wx/msgdlg.h>
43 
44 
45 #define GRID_CELL_MARGIN 4
46 
47 // Notes: These strings are static, so wxGetTranslation must be called to display the
48 // transalted text
49 static std::vector<std::pair<PCM_PACKAGE_TYPE, wxString>> PACKAGE_TYPE_LIST = {
50  { PT_PLUGIN, _( "Plugins (%d)" ) },
51  { PT_LIBRARY, _( "Libraries (%d)" ) },
52  { PT_COLORTHEME, _( "Color themes (%d)" ) },
53 };
54 
55 
56 DIALOG_PCM::DIALOG_PCM( wxWindow* parent ) : DIALOG_PCM_BASE( parent )
57 {
59 
60  m_pcm = std::make_shared<PLUGIN_CONTENT_MANAGER>( this );
61 
62  m_gridPendingActions->PushEventHandler( new GRID_TRICKS( m_gridPendingActions ) );
63 
65  m_panelPending->Layout();
66 
68  m_panelInstalledHolder->GetSizer()->Add( m_installedPanel, 1, wxEXPAND );
69  m_panelInstalledHolder->Layout();
70 
71  for( const std::pair<PCM_PACKAGE_TYPE, wxString>& entry : PACKAGE_TYPE_LIST )
72  {
74  wxString label = wxGetTranslation( entry.second );
75  m_contentNotebook->AddPage( panel, wxString::Format( label, 0 ) );
76  m_repositoryContentPanels.insert( { entry.first, panel } );
77  }
78 
79  m_dialogNotebook->SetPageText( 0, wxString::Format( _( "Repository (%d)" ), 0 ) );
80 
81  m_callback = [this]( const PACKAGE_VIEW_DATA& aData, PCM_PACKAGE_ACTION aAction,
82  const wxString& aVersion )
83  {
84  m_gridPendingActions->Freeze();
85 
86  PCM_PACKAGE_STATE new_state;
87 
88  m_gridPendingActions->AppendRows();
89  int row = m_gridPendingActions->GetNumberRows() - 1;
90 
91  m_gridPendingActions->SetCellValue( row, PENDING_COL_NAME, aData.package.name );
92  m_gridPendingActions->SetCellValue( row, PENDING_COL_REPOSITORY, aData.repository_name );
93 
94  if( aAction == PPA_INSTALL )
95  {
96  m_gridPendingActions->SetCellValue( row, PENDING_COL_ACTION, _( "Install" ) );
97  m_gridPendingActions->SetCellValue( row, PENDING_COL_VERSION, aVersion );
98 
99  m_pendingActions.emplace_back( aAction, aData.repository_id, aData.package, aVersion );
100 
101  new_state = PPS_PENDING_INSTALL;
102  }
103  else
104  {
105  m_gridPendingActions->SetCellValue( row, PENDING_COL_ACTION, _( "Uninstall" ) );
106  m_gridPendingActions->SetCellValue(
107  row, PENDING_COL_VERSION,
108  m_pcm->GetInstalledPackageVersion( aData.package.identifier ) );
109 
110  m_pendingActions.emplace_back( aAction, aData.repository_id, aData.package, aVersion );
111 
112  new_state = PPS_PENDING_UNINSTALL;
113  }
114 
115  m_gridPendingActions->Thaw();
116 
118 
119  m_installedPanel->SetPackageState( aData.package.identifier, new_state );
120 
121  for( const auto& entry : m_repositoryContentPanels )
122  entry.second->SetPackageState( aData.package.identifier, new_state );
123  };
124 
127 
128  m_dialogNotebook->SetSelection( 0 );
129 
130  // We use a sdbSizer to get platform-dependent ordering of the action buttons, but
131  // that requires us to correct the button labels here.
132  m_sdbSizer1OK->SetLabel( _( "Close" ) );
133  m_sdbSizer1Cancel->SetLabel( _( "Discard Changes" ) );
134  m_sdbSizer1Apply->SetLabel( _( "Apply Changes" ) );
135 
136  m_sdbSizer1->Layout();
137 
138  SetDefaultItem( m_sdbSizer1OK );
139 
140  Bind( wxEVT_CLOSE_WINDOW, &DIALOG_PCM::OnCloseWindow, this );
141  m_sdbSizer1Cancel->Bind( wxEVT_UPDATE_UI, &DIALOG_PCM::OnUpdateEventButtons, this );
142  m_sdbSizer1Apply->Bind( wxEVT_UPDATE_UI, &DIALOG_PCM::OnUpdateEventButtons, this );
143 
144  SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
145  KICAD_SETTINGS* app_settings = mgr.GetAppSettings<KICAD_SETTINGS>();
146 
147  m_pcm->SetRepositoryList( app_settings->m_PcmRepositories );
148 
150 
151  for( int col = 0; col < m_gridPendingActions->GetNumberCols(); col++ )
152  {
153  const wxString& heading = m_gridPendingActions->GetColLabelValue( col );
154  int headingWidth = GetTextExtent( heading ).x + 2 * GRID_CELL_MARGIN;
155 
156  // Set the minimal width to the column label size.
157  m_gridPendingActions->SetColMinimalWidth( col, headingWidth );
158  }
159 
160  // fix sizers now widgets are set.
162 }
163 
164 
166 {
167  m_gridPendingActions->PopEventHandler( true );
168 }
169 
170 
171 void DIALOG_PCM::OnUpdateEventButtons( wxUpdateUIEvent& event )
172 {
173  event.Enable( !m_pendingActions.empty() );
174 }
175 
176 
177 void DIALOG_PCM::OnCloseClicked( wxCommandEvent& event )
178 {
179  if( m_pendingActions.size() == 0
180  || wxMessageBox( _( "Are you sure you want to close the package manager "
181  "and discard pending changes?" ),
182  _( "Plugin and Content Manager" ), wxICON_QUESTION | wxYES_NO, this )
183  == wxYES )
184  {
185  EndModal( wxID_OK );
186  }
187 }
188 
189 
190 void DIALOG_PCM::OnCloseWindow( wxCloseEvent& aEvent )
191 {
192  wxCommandEvent dummy;
193 
195 }
196 
197 
198 void DIALOG_PCM::OnManageRepositoriesClicked( wxCommandEvent& event )
199 {
201 
202  std::vector<std::pair<wxString, wxString>> dialog_data;
203  std::vector<std::tuple<wxString, wxString, wxString>> repo_list = m_pcm->GetRepositoryList();
204 
205  for( const std::tuple<wxString, wxString, wxString>& repo : repo_list )
206  dialog_data.push_back( std::make_pair( std::get<1>( repo ), std::get<2>( repo ) ) );
207 
208  dialog->SetData( dialog_data );
209 
210  if( dialog->ShowModal() == wxID_SAVE )
211  {
212  dialog_data = dialog->GetData();
213  m_pcm->SetRepositoryList( dialog_data );
214 
215  SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
216  KICAD_SETTINGS* app_settings = mgr.GetAppSettings<KICAD_SETTINGS>();
217 
218  app_settings->m_PcmRepositories = std::move( dialog_data );
219 
221  }
222 
223  dialog->Destroy();
224 }
225 
226 
228 {
229  std::vector<std::tuple<wxString, wxString, wxString>> repositories = m_pcm->GetRepositoryList();
230 
231  m_choiceRepository->Clear();
232 
233  for( const std::tuple<wxString, wxString, wxString>& entry : repositories )
234  {
235  m_choiceRepository->Append( std::get<1>( entry ),
236  new wxStringClientData( std::get<0>( entry ) ) );
237  }
238 
239  if( repositories.size() > 0 )
240  {
241  m_choiceRepository->SetSelection( 0 );
242  m_selectedRepositoryId = std::get<0>( repositories[0] );
244  }
245  else
246  {
248  }
249 }
250 
251 
252 void DIALOG_PCM::OnRefreshClicked( wxCommandEvent& event )
253 {
254  m_pcm->DiscardRepositoryCache( m_selectedRepositoryId );
256 }
257 
258 
259 void DIALOG_PCM::OnInstallFromFileClicked( wxCommandEvent& event )
260 {
261  wxFileDialog open_file_dialog( this, _( "Choose package file" ), wxEmptyString, wxEmptyString,
262  ZipFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
263 
264  if( open_file_dialog.ShowModal() == wxID_CANCEL )
265  return;
266 
267  PCM_TASK_MANAGER task_manager( m_pcm );
268  task_manager.InstallFromFile( this, open_file_dialog.GetPath() );
269 
271 
272  if( !m_selectedRepositoryId.IsEmpty() )
274 }
275 
276 
277 void DIALOG_PCM::OnRepositoryChoice( wxCommandEvent& event )
278 {
279  wxStringClientData* data = static_cast<wxStringClientData*>(
280  m_choiceRepository->GetClientObject( m_choiceRepository->GetSelection() ) );
281 
282  m_selectedRepositoryId = data->GetData();
283 
285 }
286 
287 
288 void DIALOG_PCM::setRepositoryData( const wxString& aRepositoryId )
289 {
290  if( m_pcm->CacheRepository( aRepositoryId ) )
291  {
292  for( const auto& entry : m_repositoryContentPanels )
293  entry.second->ClearData();
294 
295  m_packageBitmaps = m_pcm->GetRepositoryPackageBitmaps( aRepositoryId );
296 
297  const std::vector<PCM_PACKAGE> packages = m_pcm->GetRepositoryPackages( aRepositoryId );
298 
299  std::unordered_map<PCM_PACKAGE_TYPE, std::vector<PACKAGE_VIEW_DATA>> data;
300 
301  for( const PCM_PACKAGE& pkg : packages )
302  {
303  PACKAGE_VIEW_DATA package_data( pkg );
304 
305  if( m_packageBitmaps.count( package_data.package.identifier ) > 0 )
306  package_data.bitmap = &m_packageBitmaps.at( package_data.package.identifier );
307  else
308  package_data.bitmap = &m_defaultBitmap;
309 
310  package_data.state = m_pcm->GetPackageState( aRepositoryId, pkg.identifier );
311 
312  for( const PENDING_ACTION& action : m_pendingActions )
313  {
314  if( action.package.identifier != pkg.identifier )
315  continue;
316 
317  if( action.action == PPA_INSTALL )
318  package_data.state = PPS_PENDING_INSTALL;
319  else
320  package_data.state = PPS_PENDING_UNINSTALL;
321 
322  break;
323  }
324 
325  package_data.repository_id = aRepositoryId;
326  package_data.repository_name = m_choiceRepository->GetStringSelection();
327 
328  data[pkg.type].emplace_back( package_data );
329  }
330 
331  for( size_t i = 0; i < PACKAGE_TYPE_LIST.size(); i++ )
332  {
333  PCM_PACKAGE_TYPE type = PACKAGE_TYPE_LIST[i].first;
334  const wxString& label = PACKAGE_TYPE_LIST[i].second;
335  m_repositoryContentPanels[type]->SetData( data[type], m_callback );
336  m_contentNotebook->SetPageText( i, wxString::Format( wxGetTranslation( label ),
337  (int) data[type].size() ) );
338  }
339 
340  m_dialogNotebook->SetPageText( 0, wxString::Format( _( "Repository (%d)" ),
341  (int) packages.size() ) );
342  }
343 }
344 
345 
346 void DIALOG_PCM::OnPendingActionsCellClicked( wxGridEvent& event )
347 {
348  m_gridPendingActions->ClearSelection();
349  m_gridPendingActions->SelectRow( event.GetRow() );
350 }
351 
352 
354 {
355  m_dialogNotebook->SetPageText(
356  2, wxString::Format( _( "Pending (%d)" ), (int) m_pendingActions.size() ) );
357 
358  for( int col = 0; col < m_gridPendingActions->GetNumberCols(); col++ )
359  {
360  // Set the width to see the full contents
361  m_gridPendingActions->SetColSize(
362  col, m_gridPendingActions->GetVisibleWidth( col, true, true, false ) );
363  }
364 }
365 
366 
368 {
370 
371  const std::vector<PCM_INSTALLATION_ENTRY> installed = m_pcm->GetInstalledPackages();
372  std::vector<PACKAGE_VIEW_DATA> package_list;
373 
374  m_installedBitmaps = m_pcm->GetInstalledPackageBitmaps();
375 
376  for( const PCM_INSTALLATION_ENTRY& entry : installed )
377  {
378  PACKAGE_VIEW_DATA package_data( entry );
379 
380  if( m_installedBitmaps.count( package_data.package.identifier ) > 0 )
381  package_data.bitmap = &m_installedBitmaps.at( package_data.package.identifier );
382  else
383  package_data.bitmap = &m_defaultBitmap;
384 
385  package_list.emplace_back( package_data );
386  }
387 
388  m_installedPanel->SetData( package_list, m_callback );
389 
390  m_dialogNotebook->SetPageText( 1, wxString::Format( _( "Installed (%d)" ),
391  (int) package_list.size() ) );
392 }
393 
394 
395 void DIALOG_PCM::OnApplyChangesClicked( wxCommandEvent& event )
396 {
397  if( m_pendingActions.size() == 0 )
398  return;
399 
400  m_sdbSizer1OK->Disable();
401  m_sdbSizer1Apply->Disable();
402  m_sdbSizer1Cancel->Disable();
403 
404  PCM_TASK_MANAGER task_manager( m_pcm );
405 
406  for( const PENDING_ACTION& action : m_pendingActions )
407  {
408  if( action.action == PPA_UNINSTALL )
409  task_manager.Uninstall( action.package );
410  else
411  task_manager.DownloadAndInstall( action.package, action.version, action.repository_id );
412  }
413 
414  task_manager.RunQueue( this );
415 
416  m_sdbSizer1OK->Enable();
417  m_sdbSizer1Apply->Enable();
418  m_sdbSizer1Cancel->Enable();
419 
421  wxCommandEvent dummy;
423 
424  if( !m_selectedRepositoryId.IsEmpty() )
426 }
427 
428 
429 void DIALOG_PCM::OnDiscardChangesClicked( wxCommandEvent& event )
430 {
431  m_gridPendingActions->Freeze();
432 
433  for( int i = m_pendingActions.size() - 1; i >= 0; i-- )
434  discardAction( i );
435 
437  m_gridPendingActions->Thaw();
438 }
439 
440 
441 void DIALOG_PCM::OnDiscardActionClicked( wxCommandEvent& event )
442 {
443  wxArrayInt rows = m_gridPendingActions->GetSelectedRows();
444 
445  std::sort( rows.begin(), rows.end(),
446  []( const int& a, const int& b )
447  {
448  return a > b;
449  } );
450 
451  m_gridPendingActions->Freeze();
452 
453  for( int row : rows )
454  discardAction( row );
455 
457  m_gridPendingActions->Thaw();
458 }
459 
460 
461 void DIALOG_PCM::discardAction( int aIndex )
462 {
463  m_gridPendingActions->DeleteRows( aIndex );
464 
465  PENDING_ACTION action = m_pendingActions[aIndex];
466 
467  PCM_PACKAGE_STATE state = m_pcm->GetPackageState( action.repository_id,
468  action.package.identifier );
469 
471 
472  for( const auto& entry : m_repositoryContentPanels )
473  entry.second->SetPackageState( action.package.identifier, state );
474 
475  m_pendingActions.erase( m_pendingActions.begin() + aIndex );
476 }
void SetData(const std::vector< PACKAGE_VIEW_DATA > &aPackageData, ActionCallback aCallback)
Recreates package panels and displays data.
void SetData(const std::vector< std::pair< wxString, wxString >> &aData)
void setRepositoryData(const wxString &aRepositoryId)
Gets package data from PCM and displays it on repository tab.
Definition: dialog_pcm.cpp:288
std::vector< std::pair< wxString, wxString > > GetData()
void setRepositoryListFromPcm()
Updates pending actions tab caption and content-fits the grid.
Definition: dialog_pcm.cpp:227
std::vector< PENDING_ACTION > m_pendingActions
Definition: dialog_pcm.h:116
wxButton * m_sdbSizer1OK
wxNotebook * m_dialogNotebook
PCM_PACKAGE_TYPE
< Supported package types
Definition: pcm_data.h:40
void updatePendingActionsTab()
Gets installed packages list from PCM and displays it on installed tab.
Definition: dialog_pcm.cpp:353
void Uninstall(const PCM_PACKAGE &aPackage)
Enqueue package uninstallation.
wxString repository_id
Definition: panel_package.h:35
void setInstalledPackages()
Discards specified pending action.
Definition: dialog_pcm.cpp:367
void OnPendingActionsCellClicked(wxGridEvent &event) override
Discards selected pending actions.
Definition: dialog_pcm.cpp:346
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition: grid_tricks.h:55
wxString ZipFileWildcard()
std::unordered_map< wxString, wxBitmap > m_installedBitmaps
Definition: dialog_pcm.h:99
void ClearData()
Selects full row of the clicked cell.
void OnInstallFromFileClicked(wxCommandEvent &event) override
Enqueues current pending actions in PCM_TASK_MANAGER and runs the queue.
Definition: dialog_pcm.cpp:259
void discardAction(int aIndex)
Definition: dialog_pcm.cpp:461
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
const PCM_PACKAGE package
Definition: panel_package.h:32
#define GRID_CELL_MARGIN
Definition: dialog_pcm.cpp:45
wxNotebook * m_contentNotebook
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:282
< Collection of data relevant to the package display panel Callback for (un)install button
Definition: panel_package.h:30
void InstallFromFile(wxWindow *aParent, const wxString &aFilePath)
Installs package from an archive file on disk.
static LIB_SYMBOL * dummy()
Used to draw a dummy shape when a LIB_SYMBOL is not found in library.
Definition: sch_symbol.cpp:72
void SetPackageState(const wxString &aPackageId, const PCM_PACKAGE_STATE aState) const
Set the state of package.
Implementing DIALOG_MANAGE_REPOSITORIES_BASE.
void OnDiscardActionClicked(wxCommandEvent &event) override
Handles modification of the buttons' status.
Definition: dialog_pcm.cpp:441
void OnManageRepositoriesClicked(wxCommandEvent &event) override
Discards current repo cache, fetches it anew and displays.
Definition: dialog_pcm.cpp:198
wxChoice * m_choiceRepository
DIALOG_PCM(wxWindow *parent)
Constructor.
Definition: dialog_pcm.cpp:56
Repository reference to a resource.
Definition: pcm_data.h:93
wxBitmapButton * m_discardActionButton
PANEL_PACKAGES_VIEW * m_installedPanel
Definition: dialog_pcm.h:95
T * GetAppSettings(bool aLoadNow=true)
Returns a handle to the a given settings by type If the settings have already been loaded,...
static std::vector< std::pair< PCM_PACKAGE_TYPE, wxString > > PACKAGE_TYPE_LIST
Definition: dialog_pcm.cpp:49
Definition of file extensions used in Kicad.
wxStdDialogButtonSizer * m_sdbSizer1
wxPanel * m_panelInstalledHolder
#define _(s)
~DIALOG_PCM()
Closes the window, asks user confirmation if there are pending actions.
Definition: dialog_pcm.cpp:165
wxBitmap * bitmap
Definition: panel_package.h:33
std::unordered_map< wxString, wxBitmap > m_packageBitmaps
Definition: dialog_pcm.h:98
void DownloadAndInstall(const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId)
Enqueue package download and installation.
wxString name
Definition: pcm_data.h:95
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
wxString repository_name
Definition: panel_package.h:36
wxButton * m_sdbSizer1Cancel
wxButton * m_sdbSizer1Apply
void OnRepositoryChoice(wxCommandEvent &event) override
Selects the whole row in the grid if a cell is clicked.
Definition: dialog_pcm.cpp:277
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 OnRefreshClicked(wxCommandEvent &event) override
Opens file selection dialog and installs selected package archive.
Definition: dialog_pcm.cpp:252
PCM_PACKAGE_STATE
Definition: pcm.h:52
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
ActionCallback m_callback
Definition: dialog_pcm.h:94
PCM_PACKAGE_STATE state
Definition: panel_package.h:34
wxString m_selectedRepositoryId
Definition: dialog_pcm.h:97
void OnCloseClicked(wxCommandEvent &event) override
Definition: dialog_pcm.cpp:177
see class PGM_BASE
void RunQueue(wxWindow *aParent)
Run queue of pending actions.
Class DIALOG_PCM_BASE.
Helper class that handles package (un)installation.
wxString identifier
Definition: pcm_data.h:98
wxPanel * m_panelPending
std::unordered_map< PCM_PACKAGE_TYPE, PANEL_PACKAGES_VIEW * > m_repositoryContentPanels
Definition: dialog_pcm.h:96
void OnUpdateEventButtons(wxUpdateUIEvent &event)
Definition: dialog_pcm.cpp:171
void OnDiscardChangesClicked(wxCommandEvent &event) override
Switches to another repository.
Definition: dialog_pcm.cpp:429
std::shared_ptr< PLUGIN_CONTENT_MANAGER > m_pcm
Definition: dialog_pcm.h:93
WX_GRID * m_gridPendingActions
Definition: pcm_data.h:133
void OnApplyChangesClicked(wxCommandEvent &event) override
Discards all pending changes.
Definition: dialog_pcm.cpp:395
wxBitmap m_defaultBitmap
Definition: dialog_pcm.h:100
void OnCloseWindow(wxCloseEvent &aEvent)
Opens repository management dialog, saves changes to PCM.
Definition: dialog_pcm.cpp:190
PCM_PACKAGE_ACTION
Definition: pcm.h:63