Add a column showing 3D model file loading errors

This adds an icon to the left of the row with an error symbol if the
file can't be found or opened, and shows a tooltip over the icon with
and error string.

Fixes https://gitlab.com/kicad/code/kicad/issues/3815
This commit is contained in:
Ian McInerney 2021-07-28 16:40:35 +01:00
parent 726f4d8016
commit 36d66085f5
8 changed files with 269 additions and 28 deletions

View File

@ -23,6 +23,8 @@
*/
#include <grid_tricks.h>
#include <wx/defs.h>
#include <wx/event.h>
#include <wx/tokenzr.h>
#include <wx/clipbrd.h>
#include <wx/log.h>
@ -58,6 +60,10 @@ GRID_TRICKS::GRID_TRICKS( WX_GRID* aGrid ):
aGrid->Connect( wxEVT_KEY_DOWN, wxKeyEventHandler( GRID_TRICKS::onKeyDown ), nullptr, this );
aGrid->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( GRID_TRICKS::onUpdateUI ),
nullptr, this );
// The handlers that control the tooltips must be on the actual grid window, not the grid
aGrid->GetGridWindow()->Connect( wxEVT_MOTION,
wxMouseEventHandler( GRID_TRICKS::onGridMotion ), nullptr, this );
}
@ -165,6 +171,36 @@ void GRID_TRICKS::onGridCellLeftDClick( wxGridEvent& aEvent )
}
void GRID_TRICKS::onGridMotion( wxMouseEvent& aEvent )
{
// Always skip the event
aEvent.Skip();
wxPoint pt = aEvent.GetPosition();
wxPoint pos = m_grid->CalcScrolledPosition( wxPoint( pt.x, pt.y ) );
int col = m_grid->XToCol( pos.x );
// Skip the event if the tooltip shouldn't be shown
if( !m_tooltipEnabled[col] || ( col == wxNOT_FOUND ) )
{
m_grid->GetGridWindow()->SetToolTip( "" );
return;
}
int row = m_grid->YToRow( pos.y );
if( row == wxNOT_FOUND )
{
m_grid->GetGridWindow()->SetToolTip( "" );
return;
}
// Set the tooltip to the string contained in the cell
m_grid->GetGridWindow()->SetToolTip( m_grid->GetCellValue( row, col ) );
}
bool GRID_TRICKS::handleDoubleClick( wxGridEvent& aEvent )
{
// Double-click processing must be handled by specific sub-classes

View File

@ -23,6 +23,8 @@
#include <widgets/grid_icon_text_helpers.h>
#include <wx/artprov.h>
#include <wx/defs.h>
#include <wx/textctrl.h>
#include <wx/dc.h>
@ -121,6 +123,61 @@ wxGridCellRenderer* GRID_CELL_ICON_RENDERER::Clone() const
}
//---- Grid helpers: custom wxGridCellRenderer that renders just an icon ----------------
//
// Note: this renderer is supposed to be used with read only cells
GRID_CELL_STATUS_ICON_RENDERER::GRID_CELL_STATUS_ICON_RENDERER( int aStatus ) :
m_status( aStatus )
{
if( m_status != 0 )
{
m_bitmap = wxArtProvider::GetBitmap( wxArtProvider::GetMessageBoxIconId( m_status ),
wxART_BUTTON );
}
else
{
// Dummy bitmap for size
m_bitmap = wxArtProvider::GetBitmap( wxArtProvider::GetMessageBoxIconId( wxICON_INFORMATION ),
wxART_BUTTON );
}
}
void GRID_CELL_STATUS_ICON_RENDERER::Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC,
const wxRect& aRect, int aRow, int aCol,
bool isSelected )
{
wxRect rect = aRect;
rect.Inflate( -1 );
// erase background
wxGridCellRenderer::Draw( aGrid, aAttr, aDC, aRect, aRow, aCol, isSelected );
// Draw icon
if( ( m_status != 0 ) && m_bitmap.IsOk() )
{
aDC.DrawBitmap( m_bitmap,
rect.GetLeft() + ( rect.GetWidth() - m_bitmap.GetWidth() ) / 2,
rect.GetTop() + ( rect.GetHeight() - m_bitmap.GetHeight() ) / 2,
true );
}
}
wxSize GRID_CELL_STATUS_ICON_RENDERER::GetBestSize( wxGrid& grid, wxGridCellAttr& attr, wxDC& dc,
int row, int col )
{
return wxSize( m_bitmap.GetWidth() + 6, m_bitmap.GetHeight() + 4 );
}
wxGridCellRenderer* GRID_CELL_STATUS_ICON_RENDERER::Clone() const
{
return new GRID_CELL_STATUS_ICON_RENDERER( m_status );
}
GRID_CELL_ICON_TEXT_POPUP::GRID_CELL_ICON_TEXT_POPUP( const std::vector<BITMAPS>& icons,
const wxArrayString& names ) :
m_icons( icons ),

View File

@ -26,11 +26,14 @@
#define _GRID_TRICKS_H_
#include <bitset>
#include <wx/grid.h>
#include <wx/event.h>
#include <wx/menu.h>
#include <widgets/wx_grid.h>
#define GRIDTRICKS_MAX_COL 20
enum
{
GRIDTRICKS_FIRST_ID = 901,
@ -40,9 +43,9 @@ enum
GRIDTRICKS_ID_PASTE,
GRIDTRICKS_ID_SELECT,
GRIDTRICKS_FIRST_SHOWHIDE = 979, // reserve 20 IDs for show/hide-column-n
GRIDTRICKS_FIRST_SHOWHIDE = 979, // reserve IDs for show/hide-column-n
GRIDTRICKS_LAST_ID = 999
GRIDTRICKS_LAST_ID = GRIDTRICKS_FIRST_SHOWHIDE + GRIDTRICKS_MAX_COL
};
@ -54,6 +57,30 @@ class GRID_TRICKS : public wxEvtHandler
public:
explicit GRID_TRICKS( WX_GRID* aGrid );
/**
* Enable the tooltip for a column.
*
* The tooltip is read from the string contained in the cell data.
*
* @param aCol is the column to use
* @param aEnable is true to enable the tooltip (default)
*/
void SetTooltipEnable( int aCol, bool aEnable = true )
{
m_tooltipEnabled[aCol] = aEnable;
}
/**
* Query if the tooltip for a column is enabled
*
* @param aCol is the column to query
* @return if the tooltip is enabled for the column
*/
bool GetTooltipEnabled( int aCol )
{
return m_tooltipEnabled[aCol];
}
protected:
/// Puts the selected area into a sensible rectangle of m_sel_{row,col}_{start,count} above.
void getSelectedArea();
@ -66,6 +93,7 @@ protected:
void onPopupSelection( wxCommandEvent& event );
void onKeyDown( wxKeyEvent& ev );
void onUpdateUI( wxUpdateUIEvent& event );
void onGridMotion( wxMouseEvent& event );
virtual bool handleDoubleClick( wxGridEvent& aEvent );
virtual void showPopupMenu( wxMenu& menu );
@ -86,6 +114,8 @@ protected:
int m_sel_col_start;
int m_sel_row_count;
int m_sel_col_count;
std::bitset<GRIDTRICKS_MAX_COL> m_tooltipEnabled;
};
#endif // _GRID_TRICKS_H_

View File

@ -24,6 +24,7 @@
#ifndef GRID_ICON_TEXT_HELPERS_H
#define GRID_ICON_TEXT_HELPERS_H
#include <wx/bitmap.h>
#include <wx/bmpcbox.h>
#include <wx/generic/gridctrl.h>
#include <wx/generic/grideditors.h>
@ -67,6 +68,27 @@ private:
const wxBitmap& m_icon;
};
//---- Grid helpers: custom wxGridCellRenderer that renders just an icon from wxArtprovider -
//
// Note: use with read only cells
class GRID_CELL_STATUS_ICON_RENDERER : public wxGridCellRenderer
{
public:
GRID_CELL_STATUS_ICON_RENDERER( int aStatus );
void Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC,
const wxRect& aRect, int aRow, int aCol, bool isSelected ) override;
wxSize GetBestSize( wxGrid & grid, wxGridCellAttr & attr, wxDC & dc, int row, int col ) override;
wxGridCellRenderer* Clone() const override;
private:
int m_status;
wxBitmap m_bitmap;
};
//---- Grid helpers: custom wxGridCellEditor ------------------------------------------
//
// Note: this implementation is an adaptation of wxGridCellChoiceEditor

View File

@ -31,6 +31,7 @@
#include <board_design_settings.h>
#include <board_commit.h>
#include <bitmaps.h>
#include <widgets/grid_icon_text_helpers.h>
#include <widgets/grid_text_button_helpers.h>
#include <widgets/wx_grid.h>
#include <widgets/text_ctrl_eval.h>
@ -52,6 +53,12 @@
// Remember the last open page during session.
int DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::m_page = 0;
enum MODELS_TABLE_COLUMNS
{
COL_PROBLEM = 0,
COL_FILENAME = 1,
COL_SHOWN = 2
};
DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR(
FOOTPRINT_EDIT_FRAME* aParent,
@ -87,7 +94,11 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR(
m_itemsGrid->SetTable( m_texts );
m_itemsGrid->PushEventHandler( new GRID_TRICKS( m_itemsGrid ) );
m_modelsGrid->PushEventHandler( new GRID_TRICKS( m_modelsGrid ) );
GRID_TRICKS* trick = new GRID_TRICKS( m_modelsGrid );
trick->SetTooltipEnable( COL_PROBLEM );
m_modelsGrid->PushEventHandler( trick );
// Show/hide columns according to the user's preference
m_itemsGrid->ShowHideColumns( m_frame->GetSettings()->m_FootprintTextShownColumns );
@ -99,17 +110,23 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR(
wxGetEnv( KICAD6_3DMODEL_DIR, &cfg->m_lastFootprint3dDir );
}
// Icon showing warning/error information
wxGridCellAttr* attr = new wxGridCellAttr;
attr->SetReadOnly();
m_modelsGrid->SetColAttr( COL_PROBLEM, attr );
// Filename
attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_PATH_EDITOR( this, m_modelsGrid, &cfg->m_lastFootprint3dDir,
"*.*", true, Prj().GetProjectPath() ) );
m_modelsGrid->SetColAttr( 0, attr );
m_modelsGrid->SetColAttr( COL_FILENAME, attr );
// Show checkbox
attr = new wxGridCellAttr;
attr->SetRenderer( new wxGridCellBoolRenderer() );
attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
m_modelsGrid->SetColAttr( 1, attr );
m_modelsGrid->SetColAttr( COL_SHOWN, attr );
m_modelsGrid->SetWindowStyleFlag( m_modelsGrid->GetWindowStyle() & ~wxHSCROLL );
aParent->Prj().Get3DCacheManager()->GetResolver()->SetProgramBase( &Pgm() );
@ -313,8 +330,11 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::TransferDataToWindow()
m_modelsGrid->AppendRows( 1 );
int row = m_modelsGrid->GetNumberRows() - 1;
m_modelsGrid->SetCellValue( row, 0, origPath );
m_modelsGrid->SetCellValue( row, 1, model.m_Show ? wxT( "1" ) : wxT( "0" ) );
m_modelsGrid->SetCellValue( row, COL_FILENAME, origPath );
m_modelsGrid->SetCellValue( row, COL_SHOWN, model.m_Show ? wxT( "1" ) : wxT( "0" ) );
// Must be after the filename is set
updateValidateStatus( row );
}
select3DModel( 0 ); // will clamp idx within bounds
@ -344,7 +364,7 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::TransferDataToWindow()
}
m_itemsGrid->SetRowLabelSize( m_itemsGrid->GetVisibleWidth( -1, true, true, true ) );
m_modelsGrid->SetColSize( 1, m_modelsGrid->GetVisibleWidth( 1, true, false, false ) );
m_modelsGrid->SetColSize( COL_SHOWN, m_modelsGrid->GetVisibleWidth( COL_SHOWN, true, false, false ) );
Layout();
adjustGridColumns( m_itemsGrid->GetRect().GetWidth() );
@ -363,7 +383,7 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::select3DModel( int aModelIdx )
if( m_modelsGrid->GetNumberRows() )
{
m_modelsGrid->SelectRow( aModelIdx );
m_modelsGrid->SetGridCursor( aModelIdx, 0 );
m_modelsGrid->SetGridCursor( aModelIdx, COL_FILENAME );
}
m_previewPane->SetSelectedModel( aModelIdx );
@ -381,11 +401,11 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::On3DModelSelected( wxGridEvent& aEve
void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::On3DModelCellChanged( wxGridEvent& aEvent )
{
if( aEvent.GetCol() == 0 )
if( aEvent.GetCol() == COL_FILENAME )
{
bool hasAlias = false;
FILENAME_RESOLVER* res = Prj().Get3DCacheManager()->GetResolver();
wxString filename = m_modelsGrid->GetCellValue( aEvent.GetRow(), 0 );
wxString filename = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_FILENAME );
filename.Replace( "\n", "" );
filename.Replace( "\r", "" );
@ -411,11 +431,13 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::On3DModelCellChanged( wxGridEvent& a
#endif
m_shapes3D_list[ aEvent.GetRow() ].m_Filename = filename;
m_modelsGrid->SetCellValue( aEvent.GetRow(), 0, filename );
m_modelsGrid->SetCellValue( aEvent.GetRow(), COL_FILENAME, filename );
updateValidateStatus( aEvent.GetRow() );
}
else if( aEvent.GetCol() == 1 )
else if( aEvent.GetCol() == COL_SHOWN )
{
wxString showValue = m_modelsGrid->GetCellValue( aEvent.GetRow(), 1 );
wxString showValue = m_modelsGrid->GetCellValue( aEvent.GetRow(), COL_SHOWN );
m_shapes3D_list[ aEvent.GetRow() ].m_Show = ( showValue == wxT( "1" ) );
}
@ -501,8 +523,10 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::OnAdd3DModel( wxCommandEvent& )
int idx = m_modelsGrid->GetNumberRows();
m_modelsGrid->AppendRows( 1 );
m_modelsGrid->SetCellValue( idx, 0, filename );
m_modelsGrid->SetCellValue( idx, 1, wxT( "1" ) );
m_modelsGrid->SetCellValue( idx, COL_FILENAME, filename );
m_modelsGrid->SetCellValue( idx, COL_SHOWN, wxT( "1" ) );
updateValidateStatus( idx );
select3DModel( idx );
m_previewPane->UpdateDummyFootprint();
@ -521,13 +545,14 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::OnAdd3DRow( wxCommandEvent& )
int row = m_modelsGrid->GetNumberRows();
m_modelsGrid->AppendRows( 1 );
m_modelsGrid->SetCellValue( row, 1, wxT( "1" ) );
m_modelsGrid->SetCellValue( row, COL_SHOWN, wxT( "1" ) );
m_modelsGrid->SetCellValue( row, COL_PROBLEM, "" );
select3DModel( row );
m_modelsGrid->SetFocus();
m_modelsGrid->MakeCellVisible( row, 0 );
m_modelsGrid->SetGridCursor( row, 0 );
m_modelsGrid->MakeCellVisible( row, COL_FILENAME );
m_modelsGrid->SetGridCursor( row, COL_FILENAME );
m_modelsGrid->EnableCellEditControl( true );
m_modelsGrid->ShowCellEditControl();
@ -552,6 +577,59 @@ bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::checkFootprintName( const wxString&
}
void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::updateValidateStatus( int aRow )
{
int icon = 0;
wxString errStr;
switch( validateModelExists( m_modelsGrid->GetCellValue( aRow, COL_FILENAME) ) )
{
case MODEL_VALIDATE_ERRORS::NO_ERROR:
icon = 0;
errStr = "";
break;
case MODEL_VALIDATE_ERRORS::RESOLVE_FAIL:
icon = wxICON_ERROR;
errStr = _( "File not found" );
break;
case MODEL_VALIDATE_ERRORS::OPEN_FAIL:
icon = wxICON_ERROR;
errStr = _( "Unable to open file" );
break;
default:
icon = wxICON_ERROR;
errStr = _( "Unknown error" );
break;
}
m_modelsGrid->SetCellValue( aRow, COL_PROBLEM, errStr );
m_modelsGrid->SetCellRenderer( aRow, COL_PROBLEM,
new GRID_CELL_STATUS_ICON_RENDERER( icon ) );
}
MODEL_VALIDATE_ERRORS DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::validateModelExists( const wxString& aFilename )
{
FILENAME_RESOLVER* resolv = Prj().Get3DFilenameResolver();
if( !resolv )
return MODEL_VALIDATE_ERRORS::RESOLVE_FAIL;
wxString fullPath = resolv->ResolvePath( aFilename );
if( fullPath.IsEmpty() )
return MODEL_VALIDATE_ERRORS::RESOLVE_FAIL;
if( wxFileName::IsFileReadable( fullPath ) )
return MODEL_VALIDATE_ERRORS::NO_ERROR;
else
return MODEL_VALIDATE_ERRORS::OPEN_FAIL;
}
bool DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::Validate()
{
if( !m_itemsGrid->CommitPendingChanges() )
@ -829,10 +907,15 @@ void DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR::adjustGridColumns( int aWidth )
itemsWidth -= m_itemsGrid->GetColSize( i );
if( itemsWidth > 0 )
{
m_itemsGrid->SetColSize( 0, std::max( itemsWidth,
m_itemsGrid->GetVisibleWidth( 0, true, false, false ) ) );
}
m_modelsGrid->SetColSize( 0, modelsWidth - m_modelsGrid->GetColSize( 1 ) - 5 );
int width = modelsWidth - m_modelsGrid->GetColSize( COL_SHOWN )
- m_modelsGrid->GetColSize( COL_PROBLEM ) - 5;
m_modelsGrid->SetColSize( COL_FILENAME, width );
}

View File

@ -36,6 +36,13 @@ class PANEL_PREVIEW_3D_MODEL;
class FOOTPRINT_EDIT_FRAME;
enum class MODEL_VALIDATE_ERRORS
{
NO_ERROR,
RESOLVE_FAIL,
OPEN_FAIL
};
class DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR : public DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE
{
public:
@ -62,6 +69,10 @@ private:
void OnDeleteField( wxCommandEvent& event ) override;
void OnUpdateUI( wxUpdateUIEvent& event ) override;
void updateValidateStatus( int aRow );
MODEL_VALIDATE_ERRORS validateModelExists( const wxString& aFilename );
bool checkFootprintName( const wxString& aFootprintName );
void select3DModel( int aModelIdx );

View File

@ -341,20 +341,22 @@ DIALOG_FOOTPRINT_PROPERTIES_FP_EDITOR_BASE::DIALOG_FOOTPRINT_PROPERTIES_FP_EDITO
m_modelsGrid = new WX_GRID( sbSizer3->GetStaticBox(), wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE );
// Grid
m_modelsGrid->CreateGrid( 3, 2 );
m_modelsGrid->CreateGrid( 3, 3 );
m_modelsGrid->EnableEditing( true );
m_modelsGrid->EnableGridLines( false );
m_modelsGrid->EnableDragGridSize( false );
m_modelsGrid->SetMargins( 0, 0 );
// Columns
m_modelsGrid->SetColSize( 0, 650 );
m_modelsGrid->SetColSize( 1, 65 );
m_modelsGrid->SetColSize( 0, 20 );
m_modelsGrid->SetColSize( 1, 650 );
m_modelsGrid->SetColSize( 2, 65 );
m_modelsGrid->EnableDragColMove( false );
m_modelsGrid->EnableDragColSize( false );
m_modelsGrid->SetColLabelSize( 22 );
m_modelsGrid->SetColLabelValue( 0, _("3D Model(s)") );
m_modelsGrid->SetColLabelValue( 1, _("Show") );
m_modelsGrid->SetColLabelValue( 0, wxEmptyString );
m_modelsGrid->SetColLabelValue( 1, _("3D Model(s)") );
m_modelsGrid->SetColLabelValue( 2, _("Show") );
m_modelsGrid->SetColLabelAlignment( wxALIGN_LEFT, wxALIGN_CENTER );
// Rows

View File

@ -2888,10 +2888,10 @@
<property name="close_button">1</property>
<property name="col_label_horiz_alignment">wxALIGN_LEFT</property>
<property name="col_label_size">22</property>
<property name="col_label_values">&quot;3D Model(s)&quot; &quot;Show&quot;</property>
<property name="col_label_values">&quot;&quot; &quot;3D Model(s)&quot; &quot;Show&quot;</property>
<property name="col_label_vert_alignment">wxALIGN_CENTER</property>
<property name="cols">2</property>
<property name="column_sizes">650,65</property>
<property name="cols">3</property>
<property name="column_sizes">20,650,65</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>